1 /*
2  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 #pragma ident	"%Z%%M%	%I%	%E% SMI"
6 /*
7  * prof_file.c ---- routines that manipulate an individual profile file.
8  */
9 
10 #include <autoconf.h>
11 #include "prof_int.h"
12 
13 #include <stdio.h>
14 #ifdef HAVE_STDLIB_H
15 #include <stdlib.h>
16 #endif
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif
20 #include <string.h>
21 #include <stddef.h>
22 
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <errno.h>
26 
27 #ifdef HAVE_PWD_H
28 #include <pwd.h>
29 #endif
30 
31 #if defined(_WIN32)
32 #include <io.h>
33 #define HAVE_STAT
34 #define stat _stat
35 #endif
36 
37 #include "k5-platform.h"
38 
39 struct global_shared_profile_data {
40 	/* This is the head of the global list of shared trees */
41 	prf_data_t trees;
42 	/* Lock for above list.  */
43 	k5_mutex_t mutex;
44 };
45 #define g_shared_trees		(krb5int_profile_shared_data.trees)
46 #define g_shared_trees_mutex	(krb5int_profile_shared_data.mutex)
47 
48 static struct global_shared_profile_data krb5int_profile_shared_data = {
49     0,
50     K5_MUTEX_PARTIAL_INITIALIZER
51 };
52 
53 MAKE_INIT_FUNCTION(profile_library_initializer);
54 MAKE_FINI_FUNCTION(profile_library_finalizer);
55 
56 int profile_library_initializer(void)
57 {
58 #if !USE_BUNDLE_ERROR_STRINGS
59     add_error_table(&et_prof_error_table);
60 #endif
61     return k5_mutex_finish_init(&g_shared_trees_mutex);
62 }
63 void profile_library_finalizer(void)
64 {
65     if (! INITIALIZER_RAN(profile_library_initializer) || PROGRAM_EXITING())
66 	return;
67     k5_mutex_destroy(&g_shared_trees_mutex);
68 #if !USE_BUNDLE_ERROR_STRINGS
69     remove_error_table(&et_prof_error_table);
70 #endif
71 }
72 
73 static void profile_free_file_data(prf_data_t);
74 
75 #if 0
76 
77 #define scan_shared_trees_locked()				\
78 	{							\
79 	    prf_data_t d;					\
80 	    k5_mutex_assert_locked(&g_shared_trees_mutex);	\
81 	    for (d = g_shared_trees; d; d = d->next) {		\
82 		assert(d->magic == PROF_MAGIC_FILE_DATA);	\
83 		assert((d->flags & PROFILE_FILE_SHARED) != 0);	\
84 		assert(d->filespec[0] != 0);			\
85 		assert(d->fslen <= 1000); /* XXX */		\
86 		assert(d->filespec[d->fslen] == 0);		\
87 		assert(d->fslen = strlen(d->filespec));		\
88 	    }							\
89 	}
90 
91 #define scan_shared_trees_unlocked()			\
92 	{						\
93 	    int r;					\
94 	    r = k5_mutex_lock(&g_shared_trees_mutex);	\
95 	    assert (r == 0);				\
96 	    scan_shared_trees_locked();			\
97 	    k5_mutex_unlock(&g_shared_trees_mutex);	\
98 	}
99 
100 #else
101 
102 #define scan_shared_trees_locked()	{ ; }
103 #define scan_shared_trees_unlocked()	{ ; }
104 
105 #endif
106 
107 static int rw_access(const_profile_filespec_t filespec)
108 {
109 #ifdef HAVE_ACCESS
110 	if (access(filespec, W_OK) == 0)
111 		return 1;
112 	else
113 		return 0;
114 #else
115 	/*
116 	 * We're on a substandard OS that doesn't support access.  So
117 	 * we kludge a test using stdio routines, and hope fopen
118 	 * checks the r/w permissions.
119 	 */
120 	FILE	*f;
121 
122 	f = fopen(filespec, "r+F");
123 	if (f) {
124 		fclose(f);
125 		return 1;
126 	}
127 	return 0;
128 #endif
129 }
130 
131 static int r_access(const_profile_filespec_t filespec)
132 {
133 #ifdef HAVE_ACCESS
134 	if (access(filespec, R_OK) == 0)
135 		return 1;
136 	else
137 		return 0;
138 #else
139 	/*
140 	 * We're on a substandard OS that doesn't support access.  So
141 	 * we kludge a test using stdio routines, and hope fopen
142 	 * checks the r/w permissions.
143 	 */
144 	FILE	*f;
145 
146 	f = fopen(filespec, "rF");
147 	if (f) {
148 		fclose(f);
149 		return 1;
150 	}
151 	return 0;
152 #endif
153 }
154 
155 prf_data_t
156 profile_make_prf_data(const char *filename)
157 {
158     prf_data_t d;
159     size_t len, flen, slen;
160     char *fcopy;
161 
162     flen = strlen(filename);
163     slen = offsetof(struct _prf_data_t, filespec);
164     len = slen + flen + 1;
165     if (len < sizeof(struct _prf_data_t))
166 	len = sizeof(struct _prf_data_t);
167     d = malloc(len);
168     if (d == NULL)
169 	return NULL;
170     memset(d, 0, len);
171     fcopy = (char *) d + slen;
172     assert(fcopy == d->filespec);
173     strcpy(fcopy, filename);
174     d->refcount = 1;
175     d->comment = NULL;
176     d->magic = PROF_MAGIC_FILE_DATA;
177     d->root = NULL;
178     d->next = NULL;
179     d->fslen = flen;
180     return d;
181 }
182 
183 errcode_t profile_open_file(const_profile_filespec_t filespec,
184 			    prf_file_t *ret_prof)
185 {
186 	prf_file_t	prf;
187 	errcode_t	retval;
188 	char		*home_env = 0;
189 	unsigned int	len;
190 	prf_data_t	data;
191 	char		*expanded_filename;
192 
193 	retval = CALL_INIT_FUNCTION(profile_library_initializer);
194 	if (retval)
195 		return retval;
196 
197 	scan_shared_trees_unlocked();
198 
199 	prf = (prf_file_t) malloc(sizeof(struct _prf_file_t));
200 	if (!prf)
201 		return ENOMEM;
202 	memset(prf, 0, sizeof(struct _prf_file_t));
203 	prf->magic = PROF_MAGIC_FILE;
204 
205 	len = strlen(filespec)+1;
206 	if (filespec[0] == '~' && filespec[1] == '/') {
207 		home_env = getenv("HOME");
208 #ifdef HAVE_PWD_H
209 		if (home_env == NULL) {
210 		    uid_t uid;
211 		    struct passwd *pw;
212 #ifdef HAVE_GETPWUID_R
213 		    struct passwd pwx;
214 		    char pwbuf[BUFSIZ];
215 #endif
216 
217 		    uid = getuid();
218 #ifndef HAVE_GETPWUID_R
219 		    pw = getpwuid(uid);
220 #elif defined(GETPWUID_R_4_ARGS)
221 		    /* earlier POSIX drafts */
222 		    pw = getpwuid_r(uid, &pwx, pwbuf, sizeof(pwbuf));
223 #else
224 		    /* POSIX */
225 		    if (getpwuid_r(uid, &pwx, pwbuf, sizeof(pwbuf), &pw) != 0)
226 			/* Probably already null, but let's make sure.  */
227 			pw = NULL;
228 #endif /* getpwuid variants */
229 		    if (pw != NULL && pw->pw_dir[0] != 0)
230 			home_env = pw->pw_dir;
231 		}
232 #endif
233 		if (home_env)
234 			len += strlen(home_env);
235 	}
236 	expanded_filename = malloc(len);
237 	if (expanded_filename == 0)
238 	    return errno;
239 	if (home_env) {
240 	    strcpy(expanded_filename, home_env);
241 	    strcat(expanded_filename, filespec+1);
242 	} else
243 	    memcpy(expanded_filename, filespec, len);
244 
245 	retval = k5_mutex_lock(&g_shared_trees_mutex);
246 	if (retval) {
247 	    free(expanded_filename);
248 	    free(prf);
249 	    scan_shared_trees_unlocked();
250 	    return retval;
251 	}
252 	scan_shared_trees_locked();
253 	for (data = g_shared_trees; data; data = data->next) {
254 	    if (!strcmp(data->filespec, expanded_filename)
255 		/* Check that current uid has read access.  */
256 		&& r_access(data->filespec))
257 		break;
258 	}
259 	if (data) {
260 	    retval = profile_update_file_data(data);
261 	    data->refcount++;
262 	    (void) k5_mutex_unlock(&g_shared_trees_mutex);
263 	    free(expanded_filename);
264 	    prf->data = data;
265 	    *ret_prof = prf;
266 	    scan_shared_trees_unlocked();
267 	    return retval;
268 	}
269 	(void) k5_mutex_unlock(&g_shared_trees_mutex);
270 	data = profile_make_prf_data(expanded_filename);
271 	if (data == NULL) {
272 	    free(prf);
273 	    free(expanded_filename);
274 	    return ENOMEM;
275 	}
276 	free(expanded_filename);
277 	prf->data = data;
278 
279 	retval = k5_mutex_init(&data->lock);
280 	if (retval) {
281 	    free(data);
282 	    free(prf);
283 	    return retval;
284 	}
285 
286 	retval = profile_update_file(prf);
287 	if (retval) {
288 		profile_close_file(prf);
289 		return retval;
290 	}
291 
292 	retval = k5_mutex_lock(&g_shared_trees_mutex);
293 	if (retval) {
294 	    profile_close_file(prf);
295 	    scan_shared_trees_unlocked();
296 	    return retval;
297 	}
298 	scan_shared_trees_locked();
299 	data->flags |= PROFILE_FILE_SHARED;
300 	data->next = g_shared_trees;
301 	g_shared_trees = data;
302 	scan_shared_trees_locked();
303 	(void) k5_mutex_unlock(&g_shared_trees_mutex);
304 
305 	*ret_prof = prf;
306 	return 0;
307 }
308 
309 errcode_t profile_update_file_data(prf_data_t data)
310 {
311 	errcode_t retval;
312 #ifdef HAVE_STAT
313 	struct stat st;
314 #ifdef STAT_ONCE_PER_SECOND
315 	time_t now;
316 #endif
317 #endif
318 	FILE *f;
319 
320 	retval = k5_mutex_lock(&data->lock);
321 	if (retval)
322 	    return retval;
323 
324 #ifdef HAVE_STAT
325 #ifdef STAT_ONCE_PER_SECOND
326 	now = time(0);
327 	if (now == data->last_stat) {
328 	    k5_mutex_unlock(&data->lock);
329 	    return 0;
330 	}
331 #endif
332 	if (stat(data->filespec, &st)) {
333 	    retval = errno;
334 	    k5_mutex_unlock(&data->lock);
335 	    return retval;
336 	}
337 #ifdef STAT_ONCE_PER_SECOND
338 	data->last_stat = now;
339 #endif
340 	if (st.st_mtime == data->timestamp) {
341 	    k5_mutex_unlock(&data->lock);
342 	    return 0;
343 	}
344 	if (data->root) {
345 		profile_free_node(data->root);
346 		data->root = 0;
347 	}
348 	if (data->comment) {
349 		free(data->comment);
350 		data->comment = 0;
351 	}
352 #else
353 	/*
354 	 * If we don't have the stat() call, assume that our in-core
355 	 * memory image is correct.  That is, we won't reread the
356 	 * profile file if it changes.
357 	 */
358 	if (data->root) {
359 	    k5_mutex_unlock(&data->lock);
360 	    return 0;
361 	}
362 #endif
363 	errno = 0;
364 	f = fopen(data->filespec, "rF");
365 	if (f == NULL) {
366 		retval = errno;
367 		k5_mutex_unlock(&data->lock);
368 		if (retval == 0)
369 			retval = ENOENT;
370 		return retval;
371 	}
372 	data->upd_serial++;
373 	data->flags &= PROFILE_FILE_SHARED;
374 	if (rw_access(data->filespec))
375 		data->flags |= PROFILE_FILE_RW;
376 	retval = profile_parse_file(f, &data->root);
377 	fclose(f);
378 	if (retval) {
379 	    k5_mutex_unlock(&data->lock);
380 	    return retval;
381 	}
382 #ifdef HAVE_STAT
383 	data->timestamp = st.st_mtime;
384 #endif
385 	k5_mutex_unlock(&data->lock);
386 	return 0;
387 }
388 
389 static int
390 make_hard_link(const char *oldpath, const char *newpath)
391 {
392 #ifdef _WIN32
393     return -1;
394 #else
395     return link(oldpath, newpath);
396 #endif
397 }
398 
399 static errcode_t write_data_to_file(prf_data_t data, const char *outfile,
400 				    int can_create)
401 {
402 	FILE		*f;
403 	profile_filespec_t new_file;
404 	profile_filespec_t old_file;
405 	errcode_t	retval = 0;
406 
407 	retval = ENOMEM;
408 
409 	new_file = old_file = 0;
410 	new_file = (char *) malloc(strlen(outfile) + 5);
411 	if (!new_file)
412 		goto errout;
413 	old_file = (char *) malloc(strlen(outfile) + 5);
414 	if (!old_file)
415 		goto errout;
416 
417 	sprintf(new_file, "%s.$$$", outfile);
418 	sprintf(old_file, "%s.bak", outfile);
419 
420 	errno = 0;
421 
422 	f = fopen(new_file, "wF");
423 	if (!f) {
424 		retval = errno;
425 		if (retval == 0)
426 			retval = PROF_FAIL_OPEN;
427 		goto errout;
428 	}
429 
430 	profile_write_tree_file(data->root, f);
431 	if (fclose(f) != 0) {
432 		retval = errno;
433 		goto errout;
434 	}
435 
436 	unlink(old_file);
437 	if (make_hard_link(outfile, old_file) == 0) {
438 	    /* Okay, got the hard link.  Yay.  Now we've got our
439 	       backup version, so just put the new version in
440 	       place.  */
441 	    if (rename(new_file, outfile)) {
442 		/* Weird, the rename didn't work.  But the old version
443 		   should still be in place, so no special cleanup is
444 		   needed.  */
445 		retval = errno;
446 		goto errout;
447 	    }
448 	} else if (errno == ENOENT && can_create) {
449 	    if (rename(new_file, outfile)) {
450 		retval = errno;
451 		goto errout;
452 	    }
453 	} else {
454 	    /* Couldn't make the hard link, so there's going to be a
455 	       small window where data->filespec does not refer to
456 	       either version.  */
457 #ifndef _WIN32
458 	    sync();
459 #endif
460 	    if (rename(outfile, old_file)) {
461 		retval = errno;
462 		goto errout;
463 	    }
464 	    if (rename(new_file, outfile)) {
465 		retval = errno;
466 		rename(old_file, outfile); /* back out... */
467 		goto errout;
468 	    }
469 	}
470 
471 	data->flags = 0;
472 	if (rw_access(outfile))
473 		data->flags |= PROFILE_FILE_RW;
474 	retval = 0;
475 
476 errout:
477 	if (new_file)
478 		free(new_file);
479 	if (old_file)
480 		free(old_file);
481 	return retval;
482 }
483 
484 errcode_t profile_flush_file_data_to_buffer (prf_data_t data, char **bufp)
485 {
486 	errcode_t	retval;
487 	retval = k5_mutex_lock(&data->lock);
488 	if (retval)
489 		return retval;
490 	retval = profile_write_tree_to_buffer(data->root, bufp);
491 	k5_mutex_unlock(&data->lock);
492 	return retval;
493 }
494 
495 errcode_t profile_flush_file_data(prf_data_t data)
496 {
497 	errcode_t	retval = 0;
498 
499 	if (!data || data->magic != PROF_MAGIC_FILE_DATA)
500 		return PROF_MAGIC_FILE_DATA;
501 
502 	retval = k5_mutex_lock(&data->lock);
503 	if (retval)
504 	    return retval;
505 
506 	if ((data->flags & PROFILE_FILE_DIRTY) == 0) {
507 	    k5_mutex_unlock(&data->lock);
508 	    return 0;
509 	}
510 
511 	retval = write_data_to_file(data, data->filespec, 0);
512 	k5_mutex_unlock(&data->lock);
513 	return retval;
514 }
515 
516 errcode_t profile_flush_file_data_to_file(prf_data_t data, const char *outfile)
517 {
518     errcode_t retval = 0;
519 
520     if (!data || data->magic != PROF_MAGIC_FILE_DATA)
521 	return PROF_MAGIC_FILE_DATA;
522 
523     retval = k5_mutex_lock(&data->lock);
524     if (retval)
525 	return retval;
526     retval = write_data_to_file(data, outfile, 1);
527     k5_mutex_unlock(&data->lock);
528     return retval;
529 }
530 
531 
532 
533 void profile_dereference_data(prf_data_t data)
534 {
535     int err;
536     scan_shared_trees_unlocked();
537     err = k5_mutex_lock(&g_shared_trees_mutex);
538     if (err)
539 	return;
540     profile_dereference_data_locked(data);
541     (void) k5_mutex_unlock(&g_shared_trees_mutex);
542     scan_shared_trees_unlocked();
543 }
544 void profile_dereference_data_locked(prf_data_t data)
545 {
546     data->refcount--;
547     if (data->refcount == 0)
548 	profile_free_file_data(data);
549 }
550 
551 int profile_lock_global()
552 {
553     return k5_mutex_lock(&g_shared_trees_mutex);
554 }
555 int profile_unlock_global()
556 {
557     return k5_mutex_unlock(&g_shared_trees_mutex);
558 }
559 
560 void profile_free_file(prf_file_t prf)
561 {
562     profile_dereference_data(prf->data);
563     free(prf);
564 }
565 
566 /* Call with mutex locked!  */
567 static void profile_free_file_data(prf_data_t data)
568 {
569     scan_shared_trees_locked();
570     if (data->flags & PROFILE_FILE_SHARED) {
571 	/* Remove from linked list.  */
572 	if (g_shared_trees == data)
573 	    g_shared_trees = data->next;
574 	else {
575 	    prf_data_t prev, next;
576 	    prev = g_shared_trees;
577 	    next = prev->next;
578 	    while (next) {
579 		if (next == data) {
580 		    prev->next = next->next;
581 		    break;
582 		}
583 		prev = next;
584 		next = next->next;
585 	    }
586 	}
587     }
588     if (data->root)
589 	profile_free_node(data->root);
590     if (data->comment)
591 	free(data->comment);
592     data->magic = 0;
593     k5_mutex_destroy(&data->lock);
594     free(data);
595     scan_shared_trees_locked();
596 }
597 
598 errcode_t profile_close_file(prf_file_t prf)
599 {
600 	errcode_t	retval;
601 
602 	retval = profile_flush_file(prf);
603 	if (retval)
604 		return retval;
605 	profile_free_file(prf);
606 	return 0;
607 }
608