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