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