xref: /freebsd/crypto/krb5/src/util/profile/prof_file.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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