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 return d;
163 }
164
profile_open_file(const_profile_filespec_t filespec,prf_file_t * ret_prof,char ** ret_modspec)165 errcode_t profile_open_file(const_profile_filespec_t filespec,
166 prf_file_t *ret_prof, char **ret_modspec)
167 {
168 prf_file_t prf;
169 errcode_t retval;
170 char *home_env = 0;
171 prf_data_t data;
172 char *expanded_filename;
173
174 retval = CALL_INIT_FUNCTION(profile_library_initializer);
175 if (retval)
176 return retval;
177
178 prf = malloc(sizeof(struct _prf_file_t));
179 if (!prf)
180 return ENOMEM;
181 memset(prf, 0, sizeof(struct _prf_file_t));
182 prf->magic = PROF_MAGIC_FILE;
183
184 if (filespec[0] == '~' && filespec[1] == '/') {
185 home_env = secure_getenv("HOME");
186 #ifdef HAVE_PWD_H
187 if (home_env == NULL) {
188 uid_t uid;
189 struct passwd *pw, pwx;
190 char pwbuf[BUFSIZ];
191
192 uid = getuid();
193 if (!k5_getpwuid_r(uid, &pwx, pwbuf, sizeof(pwbuf), &pw)
194 && pw != NULL && pw->pw_dir[0] != 0)
195 home_env = pw->pw_dir;
196 }
197 #endif
198 }
199 if (home_env) {
200 if (asprintf(&expanded_filename, "%s%s", home_env,
201 filespec + 1) < 0)
202 expanded_filename = 0;
203 } else
204 expanded_filename = strdup(filespec);
205 if (expanded_filename == 0) {
206 free(prf);
207 return ENOMEM;
208 }
209
210 k5_mutex_lock(&g_shared_trees_mutex);
211 for (data = g_shared_trees; data; data = data->next) {
212 if (!strcmp(data->filespec, expanded_filename)
213 /* Check that current uid has read access. */
214 && r_access(data->filespec))
215 break;
216 }
217 if (data) {
218 data->refcount++;
219 data->last_stat = 0; /* Make sure to stat when updating. */
220 k5_mutex_unlock(&g_shared_trees_mutex);
221 retval = profile_update_file_data(data, NULL);
222 free(expanded_filename);
223 if (retval) {
224 profile_dereference_data(data);
225 free(prf);
226 return retval;
227 }
228 prf->data = data;
229 *ret_prof = prf;
230 return 0;
231 }
232 k5_mutex_unlock(&g_shared_trees_mutex);
233 data = profile_make_prf_data(expanded_filename);
234 if (data == NULL) {
235 free(prf);
236 free(expanded_filename);
237 return ENOMEM;
238 }
239 free(expanded_filename);
240 prf->data = data;
241
242 retval = k5_mutex_init(&data->lock);
243 if (retval) {
244 free(data);
245 free(prf);
246 return retval;
247 }
248
249 retval = profile_update_file(prf, ret_modspec);
250 if (retval) {
251 profile_close_file(prf);
252 return retval;
253 }
254
255 k5_mutex_lock(&g_shared_trees_mutex);
256 data->flags |= PROFILE_FILE_SHARED;
257 data->next = g_shared_trees;
258 g_shared_trees = data;
259 k5_mutex_unlock(&g_shared_trees_mutex);
260
261 *ret_prof = prf;
262 return 0;
263 }
264
profile_update_file_data_locked(prf_data_t data,char ** ret_modspec)265 errcode_t profile_update_file_data_locked(prf_data_t data, char **ret_modspec)
266 {
267 errcode_t retval;
268 #ifdef HAVE_STAT
269 struct stat st;
270 unsigned long frac;
271 time_t now;
272 #endif
273 FILE *f;
274 int isdir = 0;
275
276 if ((data->flags & PROFILE_FILE_NO_RELOAD) && data->root != NULL)
277 return 0;
278
279 #ifdef HAVE_STAT
280 now = time(0);
281 if (now == data->last_stat && data->root != NULL) {
282 return 0;
283 }
284 if (stat(data->filespec, &st)) {
285 return errno;
286 }
287 data->last_stat = now;
288 #if defined HAVE_STRUCT_STAT_ST_MTIMENSEC
289 frac = st.st_mtimensec;
290 #elif defined HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
291 frac = st.st_mtimespec.tv_nsec;
292 #elif defined HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
293 frac = st.st_mtim.tv_nsec;
294 #else
295 frac = 0;
296 #endif
297 if (st.st_mtime == data->timestamp
298 && frac == data->frac_ts
299 && data->root != NULL) {
300 return 0;
301 }
302 if (data->root) {
303 profile_free_node(data->root);
304 data->root = 0;
305 }
306
307 /* Only try to reload regular files, not devices such as pipes. */
308 if ((st.st_mode & S_IFMT) != S_IFREG)
309 data->flags |= PROFILE_FILE_NO_RELOAD;
310 #else
311 /*
312 * If we don't have the stat() call, assume that our in-core
313 * memory image is correct. That is, we won't reread the
314 * profile file if it changes.
315 */
316 if (data->root) {
317 return 0;
318 }
319 #endif
320
321 #ifdef HAVE_STAT
322 isdir = S_ISDIR(st.st_mode);
323 #endif
324 if (!isdir) {
325 errno = 0;
326 f = fopen(data->filespec, "r");
327 if (f == NULL)
328 return (errno != 0) ? errno : ENOENT;
329 set_cloexec_file(f);
330 }
331
332 data->upd_serial++;
333 data->flags &= ~PROFILE_FILE_DIRTY;
334
335 if (isdir) {
336 retval = profile_process_directory(data->filespec, &data->root);
337 } else {
338 retval = profile_parse_file(f, &data->root, ret_modspec);
339 (void)fclose(f);
340 }
341 if (retval) {
342 return retval;
343 }
344 assert(data->root != NULL);
345 #ifdef HAVE_STAT
346 data->timestamp = st.st_mtime;
347 data->frac_ts = frac;
348 #endif
349 return 0;
350 }
351
profile_update_file_data(prf_data_t data,char ** ret_modspec)352 errcode_t profile_update_file_data(prf_data_t data, char **ret_modspec)
353 {
354 errcode_t retval;
355
356 k5_mutex_lock(&data->lock);
357 retval = profile_update_file_data_locked(data, ret_modspec);
358 k5_mutex_unlock(&data->lock);
359 return retval;
360 }
361
362 static int
make_hard_link(const char * oldpath,const char * newpath)363 make_hard_link(const char *oldpath, const char *newpath)
364 {
365 #ifdef _WIN32
366 return -1;
367 #else
368 return link(oldpath, newpath);
369 #endif
370 }
371
write_data_to_file(prf_data_t data,const char * outfile,int can_create)372 static errcode_t write_data_to_file(prf_data_t data, const char *outfile,
373 int can_create)
374 {
375 FILE *f;
376 profile_filespec_t new_file;
377 profile_filespec_t old_file;
378 errcode_t retval = 0;
379
380 retval = ENOMEM;
381
382 new_file = old_file = 0;
383 if (asprintf(&new_file, "%s.$$$", outfile) < 0) {
384 new_file = NULL;
385 goto errout;
386 }
387 if (asprintf(&old_file, "%s.bak", outfile) < 0) {
388 old_file = NULL;
389 goto errout;
390 }
391
392 errno = 0;
393
394 f = fopen(new_file, "w");
395 if (!f) {
396 retval = errno;
397 if (retval == 0)
398 retval = PROF_FAIL_OPEN;
399 goto errout;
400 }
401
402 set_cloexec_file(f);
403 profile_write_tree_file(data->root, f);
404 if (fclose(f) != 0) {
405 retval = errno;
406 goto errout;
407 }
408
409 unlink(old_file);
410 if (make_hard_link(outfile, old_file) == 0) {
411 /* Okay, got the hard link. Yay. Now we've got our
412 backup version, so just put the new version in
413 place. */
414 if (rename(new_file, outfile)) {
415 /* Weird, the rename didn't work. But the old version
416 should still be in place, so no special cleanup is
417 needed. */
418 retval = errno;
419 goto errout;
420 }
421 } else if (errno == ENOENT && can_create) {
422 if (rename(new_file, outfile)) {
423 retval = errno;
424 goto errout;
425 }
426 } else {
427 /* Couldn't make the hard link, so there's going to be a
428 small window where data->filespec does not refer to
429 either version. */
430 #ifndef _WIN32
431 sync();
432 #endif
433 if (rename(outfile, old_file)) {
434 retval = errno;
435 goto errout;
436 }
437 if (rename(new_file, outfile)) {
438 retval = errno;
439 rename(old_file, outfile); /* back out... */
440 goto errout;
441 }
442 }
443
444 retval = 0;
445
446 errout:
447 if (new_file)
448 free(new_file);
449 if (old_file)
450 free(old_file);
451 return retval;
452 }
453
profile_flush_file_data_to_buffer(prf_data_t data,char ** bufp)454 errcode_t profile_flush_file_data_to_buffer (prf_data_t data, char **bufp)
455 {
456 errcode_t retval;
457
458 k5_mutex_lock(&data->lock);
459 retval = profile_write_tree_to_buffer(data->root, bufp);
460 k5_mutex_unlock(&data->lock);
461 return retval;
462 }
463
profile_flush_file_data(prf_data_t data)464 errcode_t profile_flush_file_data(prf_data_t data)
465 {
466 errcode_t retval = 0;
467
468 if (!data || data->magic != PROF_MAGIC_FILE_DATA)
469 return PROF_MAGIC_FILE_DATA;
470
471 k5_mutex_lock(&data->lock);
472
473 if ((data->flags & PROFILE_FILE_DIRTY) == 0) {
474 k5_mutex_unlock(&data->lock);
475 return 0;
476 }
477
478 retval = write_data_to_file(data, data->filespec, 0);
479 data->flags &= ~PROFILE_FILE_DIRTY;
480 k5_mutex_unlock(&data->lock);
481 return retval;
482 }
483
profile_flush_file_data_to_file(prf_data_t data,const char * outfile)484 errcode_t profile_flush_file_data_to_file(prf_data_t data, const char *outfile)
485 {
486 errcode_t retval = 0;
487
488 if (!data || data->magic != PROF_MAGIC_FILE_DATA)
489 return PROF_MAGIC_FILE_DATA;
490
491 k5_mutex_lock(&data->lock);
492 retval = write_data_to_file(data, outfile, 1);
493 k5_mutex_unlock(&data->lock);
494 return retval;
495 }
496
497
498
profile_dereference_data(prf_data_t data)499 void profile_dereference_data(prf_data_t data)
500 {
501 k5_mutex_lock(&g_shared_trees_mutex);
502 profile_dereference_data_locked(data);
503 k5_mutex_unlock(&g_shared_trees_mutex);
504 }
profile_dereference_data_locked(prf_data_t data)505 void profile_dereference_data_locked(prf_data_t data)
506 {
507 data->refcount--;
508 if (data->refcount == 0)
509 profile_free_file_data(data);
510 }
511
profile_lock_global()512 void profile_lock_global()
513 {
514 k5_mutex_lock(&g_shared_trees_mutex);
515 }
profile_unlock_global()516 void profile_unlock_global()
517 {
518 k5_mutex_unlock(&g_shared_trees_mutex);
519 }
520
profile_free_file(prf_file_t prf)521 void profile_free_file(prf_file_t prf)
522 {
523 profile_dereference_data(prf->data);
524 free(prf);
525 }
526
527 /* Call with mutex locked! */
profile_free_file_data(prf_data_t data)528 static void profile_free_file_data(prf_data_t data)
529 {
530 if (data->flags & PROFILE_FILE_SHARED) {
531 /* Remove from linked list. */
532 if (g_shared_trees == data)
533 g_shared_trees = data->next;
534 else {
535 prf_data_t prev, next;
536 prev = g_shared_trees;
537 next = prev->next;
538 while (next) {
539 if (next == data) {
540 prev->next = next->next;
541 break;
542 }
543 prev = next;
544 next = next->next;
545 }
546 }
547 }
548 if (data->root)
549 profile_free_node(data->root);
550 data->magic = 0;
551 k5_mutex_destroy(&data->lock);
552 free(data);
553 }
554
profile_close_file(prf_file_t prf)555 errcode_t profile_close_file(prf_file_t prf)
556 {
557 errcode_t retval;
558
559 retval = profile_flush_file(prf);
560 if (retval)
561 return retval;
562 profile_free_file(prf);
563 return 0;
564 }
565