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