xref: /freebsd/crypto/krb5/src/lib/kdb/kdb_log.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
4  * Use is subject to license terms.
5  */
6 
7 #include <sys/stat.h>
8 #include <sys/types.h>
9 #include <unistd.h>
10 #include <fcntl.h>
11 #include <sys/mman.h>
12 #include <k5-int.h>
13 #include <stdlib.h>
14 #include <limits.h>
15 #include <syslog.h>
16 #include "kdb5.h"
17 #include "kdb_log.h"
18 #include "kdb5int.h"
19 
20 #ifndef MAP_FAILED
21 #define MAP_FAILED ((void *)-1)
22 #endif
23 
24 /* This module includes all the necessary functions that create and modify the
25  * Kerberos principal update and header logs. */
26 
27 #define getpagesize() sysconf(_SC_PAGESIZE)
28 
29 static int pagesize = 0;
30 
31 #define INIT_ULOG(ctx)                          \
32     log_ctx = ctx->kdblog_context;              \
33     assert(log_ctx != NULL);                    \
34     ulog = log_ctx->ulog;                       \
35     assert(ulog != NULL)
36 
37 /* Initialize context->kdblog_context if it does not yet exist, and return it.
38  * Return NULL on allocation failure. */
39 static kdb_log_context *
create_log_context(krb5_context context)40 create_log_context(krb5_context context)
41 {
42     kdb_log_context *log_ctx;
43 
44     if (context->kdblog_context != NULL)
45         return context->kdblog_context;
46     log_ctx = calloc(1, sizeof(*log_ctx));
47     if (log_ctx == NULL)
48         return NULL;
49     log_ctx->ulogfd = -1;
50     context->kdblog_context = log_ctx;
51     return log_ctx;
52 }
53 
54 static inline krb5_boolean
time_equal(const kdbe_time_t * a,const kdbe_time_t * b)55 time_equal(const kdbe_time_t *a, const kdbe_time_t *b)
56 {
57     return a->seconds == b->seconds && a->useconds == b->useconds;
58 }
59 
60 static void
time_current(kdbe_time_t * out)61 time_current(kdbe_time_t *out)
62 {
63     struct timeval timestamp;
64 
65     (void)gettimeofday(&timestamp, NULL);
66     out->seconds = timestamp.tv_sec;
67     out->useconds = timestamp.tv_usec;
68 }
69 
70 /* Sync update entry to disk. */
71 static void
sync_update(kdb_hlog_t * ulog,kdb_ent_header_t * upd)72 sync_update(kdb_hlog_t *ulog, kdb_ent_header_t *upd)
73 {
74     unsigned long start, end, size;
75 
76     if (!pagesize)
77         pagesize = getpagesize();
78 
79     start = (unsigned long)upd & ~(pagesize - 1);
80 
81     end = ((unsigned long)upd + ulog->kdb_block + (pagesize - 1)) &
82         ~(pagesize - 1);
83 
84     size = end - start;
85     if (msync((caddr_t)start, size, MS_SYNC)) {
86         /* Couldn't sync to disk, let's panic. */
87         syslog(LOG_ERR, _("could not sync ulog update to disk"));
88         abort();
89     }
90 }
91 
92 /* Sync memory to disk for the update log header. */
93 static void
sync_header(kdb_hlog_t * ulog)94 sync_header(kdb_hlog_t *ulog)
95 {
96     if (!pagesize)
97         pagesize = getpagesize();
98 
99     if (msync((caddr_t)ulog, pagesize, MS_SYNC)) {
100         /* Couldn't sync to disk, let's panic. */
101         syslog(LOG_ERR, _("could not sync ulog header to disk"));
102         abort();
103     }
104 }
105 
106 /* Return true if the ulog entry for sno matches sno and timestamp. */
107 static krb5_boolean
check_sno(kdb_log_context * log_ctx,kdb_sno_t sno,const kdbe_time_t * timestamp)108 check_sno(kdb_log_context *log_ctx, kdb_sno_t sno,
109           const kdbe_time_t *timestamp)
110 {
111     unsigned int indx = (sno - 1) % log_ctx->ulogentries;
112     kdb_ent_header_t *ent = INDEX(log_ctx->ulog, indx);
113 
114     return ent->kdb_entry_sno == sno && time_equal(&ent->kdb_time, timestamp);
115 }
116 
117 /*
118  * Check last against our ulog and determine whether it is up to date
119  * (UPDATE_NIL), so far out of date that a full dump is required
120  * (UPDATE_FULL_RESYNC_NEEDED), or okay to update with ulog entries
121  * (UPDATE_OK).
122  */
123 static update_status_t
get_sno_status(kdb_log_context * log_ctx,const kdb_last_t * last)124 get_sno_status(kdb_log_context *log_ctx, const kdb_last_t *last)
125 {
126     kdb_hlog_t *ulog = log_ctx->ulog;
127 
128     /* If last matches the ulog's last serial number and time exactly, it are
129      * up to date even if the ulog is empty. */
130     if (last->last_sno == ulog->kdb_last_sno &&
131         time_equal(&last->last_time, &ulog->kdb_last_time))
132         return UPDATE_NIL;
133 
134     /* If our ulog is empty or does not contain last_sno, a full resync is
135      * required. */
136     if (ulog->kdb_num == 0 || last->last_sno > ulog->kdb_last_sno ||
137         last->last_sno < ulog->kdb_first_sno)
138         return UPDATE_FULL_RESYNC_NEEDED;
139 
140     /* If the timestamp in our ulog entry does not match last, then sno was
141      * reused and a full resync is required. */
142     if (!check_sno(log_ctx, last->last_sno, &last->last_time))
143         return UPDATE_FULL_RESYNC_NEEDED;
144 
145     /* last is not fully up to date, but can be updated using our ulog. */
146     return UPDATE_OK;
147 }
148 
149 /* Extend update log file. */
150 static krb5_error_code
extend_file_to(int fd,unsigned int new_size)151 extend_file_to(int fd, unsigned int new_size)
152 {
153     off_t current_offset;
154     static const char zero[512];
155     ssize_t wrote_size;
156     size_t write_size;
157 
158     current_offset = lseek(fd, 0, SEEK_END);
159     if (current_offset < 0)
160         return errno;
161     if (new_size > INT_MAX)
162         return EINVAL;
163     while (current_offset < (off_t)new_size) {
164         write_size = new_size - current_offset;
165         if (write_size > 512)
166             write_size = 512;
167         wrote_size = write(fd, zero, write_size);
168         if (wrote_size < 0)
169             return errno;
170         if (wrote_size == 0)
171             return EINVAL;
172         current_offset += wrote_size;
173         write_size = new_size - current_offset;
174     }
175     return 0;
176 }
177 
178 /*
179  * Resize the array elements.  We reinitialize the update log rather than
180  * unrolling the the log and copying it over to a temporary log for obvious
181  * performance reasons.  Replicas will subsequently do a full resync, but the
182  * need for resizing should be very small.
183  */
184 static krb5_error_code
resize(kdb_hlog_t * ulog,uint32_t ulogentries,int ulogfd,unsigned int recsize)185 resize(kdb_hlog_t *ulog, uint32_t ulogentries, int ulogfd,
186        unsigned int recsize)
187 {
188     unsigned int new_block, new_size;
189 
190     if (ulog == NULL)
191         return KRB5_LOG_ERROR;
192 
193     new_size = sizeof(kdb_hlog_t);
194     new_block = (recsize / ULOG_BLOCK) + 1;
195     new_block *= ULOG_BLOCK;
196     new_size += ulogentries * new_block;
197 
198     if (new_size > MAXLOGLEN)
199         return KRB5_LOG_ERROR;
200 
201     /* Reinit log with new block size. */
202     memset(ulog, 0, sizeof(*ulog));
203     ulog->kdb_hmagic = KDB_ULOG_HDR_MAGIC;
204     ulog->db_version_num = KDB_VERSION;
205     ulog->kdb_state = KDB_STABLE;
206     ulog->kdb_block = new_block;
207     sync_header(ulog);
208 
209     /* Expand log considering new block size. */
210     return extend_file_to(ulogfd, new_size);
211 }
212 
213 /* Set the ulog to contain only a dummy entry with the given serial number and
214  * timestamp. */
215 static void
set_dummy(kdb_log_context * log_ctx,kdb_sno_t sno,const kdbe_time_t * kdb_time)216 set_dummy(kdb_log_context *log_ctx, kdb_sno_t sno, const kdbe_time_t *kdb_time)
217 {
218     kdb_hlog_t *ulog = log_ctx->ulog;
219     kdb_ent_header_t *ent = INDEX(ulog, (sno - 1) % log_ctx->ulogentries);
220 
221     memset(ent, 0, sizeof(*ent));
222     ent->kdb_umagic = KDB_ULOG_MAGIC;
223     ent->kdb_entry_sno = sno;
224     ent->kdb_time = *kdb_time;
225     sync_update(ulog, ent);
226 
227     ulog->kdb_num = 1;
228     ulog->kdb_first_sno = ulog->kdb_last_sno = sno;
229     ulog->kdb_first_time = ulog->kdb_last_time = *kdb_time;
230 }
231 
232 /* Reinitialize the ulog header, starting from sno 1 with the current time. */
233 static void
reset_ulog(kdb_log_context * log_ctx)234 reset_ulog(kdb_log_context *log_ctx)
235 {
236     kdbe_time_t kdb_time;
237     kdb_hlog_t *ulog = log_ctx->ulog;
238 
239     memset(ulog, 0, sizeof(*ulog));
240     ulog->kdb_hmagic = KDB_ULOG_HDR_MAGIC;
241     ulog->db_version_num = KDB_VERSION;
242     ulog->kdb_block = ULOG_BLOCK;
243 
244     /* Create a dummy entry to remember the timestamp for downstreams. */
245     time_current(&kdb_time);
246     set_dummy(log_ctx, 1, &kdb_time);
247     ulog->kdb_state = KDB_STABLE;
248     sync_header(ulog);
249 }
250 
251 /*
252  * If any database operations will be invoked while the ulog lock is held, the
253  * caller must explicitly lock the database before locking the ulog, or
254  * deadlock may result.
255  */
256 static krb5_error_code
lock_ulog(krb5_context context,int mode)257 lock_ulog(krb5_context context, int mode)
258 {
259     kdb_log_context *log_ctx = NULL;
260     kdb_hlog_t *ulog = NULL;
261 
262     INIT_ULOG(context);
263     return krb5_lock_file(context, log_ctx->ulogfd, mode);
264 }
265 
266 static void
unlock_ulog(krb5_context context)267 unlock_ulog(krb5_context context)
268 {
269     (void)lock_ulog(context, KRB5_LOCKMODE_UNLOCK);
270 }
271 
272 /*
273  * Add an update to the log.  The update's kdb_entry_sno and kdb_time fields
274  * must already be set.  The layout of the update log looks like:
275  *
276  * header log -> [ update header -> xdr(kdb_incr_update_t) ], ...
277  */
278 static krb5_error_code
store_update(kdb_log_context * log_ctx,kdb_incr_update_t * upd)279 store_update(kdb_log_context *log_ctx, kdb_incr_update_t *upd)
280 {
281     XDR xdrs;
282     kdb_ent_header_t *indx_log;
283     unsigned int i, recsize;
284     unsigned long upd_size;
285     krb5_error_code retval;
286     kdb_hlog_t *ulog = log_ctx->ulog;
287     uint32_t ulogentries = log_ctx->ulogentries;
288 
289     upd_size = xdr_sizeof((xdrproc_t)xdr_kdb_incr_update_t, upd);
290 
291     recsize = sizeof(kdb_ent_header_t) + upd_size;
292 
293     if (recsize > ulog->kdb_block) {
294         retval = resize(ulog, ulogentries, log_ctx->ulogfd, recsize);
295         if (retval)
296             return retval;
297     }
298 
299     ulog->kdb_state = KDB_UNSTABLE;
300 
301     i = (upd->kdb_entry_sno - 1) % ulogentries;
302     indx_log = INDEX(ulog, i);
303 
304     memset(indx_log, 0, ulog->kdb_block);
305     indx_log->kdb_umagic = KDB_ULOG_MAGIC;
306     indx_log->kdb_entry_size = upd_size;
307     indx_log->kdb_entry_sno = upd->kdb_entry_sno;
308     indx_log->kdb_time = upd->kdb_time;
309     indx_log->kdb_commit = FALSE;
310 
311     xdrmem_create(&xdrs, (char *)indx_log->entry_data,
312                   indx_log->kdb_entry_size, XDR_ENCODE);
313     if (!xdr_kdb_incr_update_t(&xdrs, upd))
314         return KRB5_LOG_CONV;
315 
316     indx_log->kdb_commit = TRUE;
317     sync_update(ulog, indx_log);
318 
319     /* Modify the ulog header to reflect the new update. */
320     ulog->kdb_last_sno = upd->kdb_entry_sno;
321     ulog->kdb_last_time = upd->kdb_time;
322     if (ulog->kdb_num == 0) {
323         /* We should only see this in old ulogs. */
324         ulog->kdb_num = 1;
325         ulog->kdb_first_sno = upd->kdb_entry_sno;
326         ulog->kdb_first_time = upd->kdb_time;
327     } else if (ulog->kdb_num < ulogentries) {
328         ulog->kdb_num++;
329     } else {
330         /* We are circling; set kdb_first_sno and time to the next update. */
331         i = upd->kdb_entry_sno % ulogentries;
332         indx_log = INDEX(ulog, i);
333         ulog->kdb_first_sno = indx_log->kdb_entry_sno;
334         ulog->kdb_first_time = indx_log->kdb_time;
335     }
336 
337     ulog->kdb_state = KDB_STABLE;
338     sync_header(ulog);
339     return 0;
340 }
341 
342 /* Add an entry to the update log. */
343 krb5_error_code
ulog_add_update(krb5_context context,kdb_incr_update_t * upd)344 ulog_add_update(krb5_context context, kdb_incr_update_t *upd)
345 {
346     krb5_error_code ret;
347     kdb_log_context *log_ctx;
348     kdb_hlog_t *ulog;
349 
350     INIT_ULOG(context);
351     ret = lock_ulog(context, KRB5_LOCKMODE_EXCLUSIVE);
352     if (ret)
353         return ret;
354 
355     /* If we have reached the last possible serial number, reinitialize the
356      * ulog and start over.  Replicas will do a full resync. */
357     if (ulog->kdb_last_sno == (kdb_sno_t)-1)
358         reset_ulog(log_ctx);
359 
360     upd->kdb_entry_sno = ulog->kdb_last_sno + 1;
361     time_current(&upd->kdb_time);
362     ret = store_update(log_ctx, upd);
363     unlock_ulog(context);
364     return ret;
365 }
366 
367 /* Used by the replica to update its hash db from the incr update log. */
368 krb5_error_code
ulog_replay(krb5_context context,kdb_incr_result_t * incr_ret,char ** db_args)369 ulog_replay(krb5_context context, kdb_incr_result_t *incr_ret, char **db_args)
370 {
371     krb5_db_entry *entry = NULL;
372     kdb_incr_update_t *upd = NULL, *fupd;
373     int i, no_of_updates;
374     krb5_error_code retval;
375     krb5_principal dbprinc;
376     char *dbprincstr;
377     kdb_log_context *log_ctx;
378     kdb_hlog_t *ulog = NULL;
379 
380     INIT_ULOG(context);
381 
382     retval = krb5_db_open(context, db_args,
383                           KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_ADMIN);
384     if (retval)
385         return retval;
386 
387     no_of_updates = incr_ret->updates.kdb_ulog_t_len;
388     upd = incr_ret->updates.kdb_ulog_t_val;
389     fupd = upd;
390 
391     for (i = 0; i < no_of_updates; i++) {
392         if (!upd->kdb_commit)
393             continue;
394 
395         /* Replay this update in the database. */
396         if (upd->kdb_deleted) {
397             dbprincstr = k5memdup0(upd->kdb_princ_name.utf8str_t_val,
398                                    upd->kdb_princ_name.utf8str_t_len, &retval);
399             if (dbprincstr == NULL)
400                 goto cleanup;
401 
402             retval = krb5_parse_name(context, dbprincstr, &dbprinc);
403             free(dbprincstr);
404             if (retval)
405                 goto cleanup;
406 
407             retval = krb5int_delete_principal_no_log(context, dbprinc);
408             krb5_free_principal(context, dbprinc);
409             if (retval == KRB5_KDB_NOENTRY)
410                 retval = 0;
411             if (retval)
412                 goto cleanup;
413         } else {
414             retval = ulog_conv_2dbentry(context, &entry, upd);
415             if (retval)
416                 goto cleanup;
417 
418             retval = krb5int_put_principal_no_log(context, entry);
419             krb5_db_free_principal(context, entry);
420             if (retval)
421                 goto cleanup;
422         }
423 
424         retval = lock_ulog(context, KRB5_LOCKMODE_EXCLUSIVE);
425         if (retval)
426             goto cleanup;
427 
428         /* If (unexpectedly) this update does not follow the last one we
429          * stored, discard any previous ulog state. */
430         if (ulog->kdb_num != 0 && upd->kdb_entry_sno != ulog->kdb_last_sno + 1)
431             reset_ulog(log_ctx);
432 
433         /* Store this update in the ulog for any downstream KDCs. */
434         retval = store_update(log_ctx, upd);
435         unlock_ulog(context);
436         if (retval)
437             goto cleanup;
438 
439         upd++;
440     }
441 
442 cleanup:
443     if (retval)
444         (void)ulog_init_header(context);
445     if (fupd)
446         ulog_free_entries(fupd, no_of_updates);
447     return retval;
448 }
449 
450 /* Reinitialize the log header. */
451 krb5_error_code
ulog_init_header(krb5_context context)452 ulog_init_header(krb5_context context)
453 {
454     krb5_error_code ret;
455     kdb_log_context *log_ctx;
456     kdb_hlog_t *ulog;
457 
458     INIT_ULOG(context);
459     ret = lock_ulog(context, KRB5_LOCKMODE_EXCLUSIVE);
460     if (ret)
461         return ret;
462     reset_ulog(log_ctx);
463     unlock_ulog(context);
464     return 0;
465 }
466 
467 /* Map the log file to memory for performance and simplicity. */
468 krb5_error_code
ulog_map(krb5_context context,const char * logname,uint32_t ulogentries)469 ulog_map(krb5_context context, const char *logname, uint32_t ulogentries)
470 {
471     struct stat st;
472     krb5_error_code retval;
473     uint32_t filesize;
474     kdb_log_context *log_ctx;
475     kdb_hlog_t *ulog = NULL;
476     krb5_boolean locked = FALSE;
477 
478     log_ctx = create_log_context(context);
479     if (log_ctx == NULL)
480         return ENOMEM;
481 
482     if (stat(logname, &st) == -1) {
483         log_ctx->ulogfd = open(logname, O_RDWR | O_CREAT, 0600);
484         if (log_ctx->ulogfd == -1) {
485             retval = errno;
486             goto cleanup;
487         }
488 
489         filesize = sizeof(kdb_hlog_t) + ulogentries * ULOG_BLOCK;
490         retval = extend_file_to(log_ctx->ulogfd, filesize);
491         if (retval)
492             goto cleanup;
493     } else {
494         log_ctx->ulogfd = open(logname, O_RDWR, 0600);
495         if (log_ctx->ulogfd == -1) {
496             retval = errno;
497             goto cleanup;
498         }
499     }
500 
501     ulog = mmap(0, MAXLOGLEN, PROT_READ | PROT_WRITE, MAP_SHARED,
502                 log_ctx->ulogfd, 0);
503     if (ulog == MAP_FAILED) {
504         retval = errno;
505         goto cleanup;
506     }
507     log_ctx->ulog = ulog;
508     log_ctx->ulogentries = ulogentries;
509 
510     retval = lock_ulog(context, KRB5_LOCKMODE_EXCLUSIVE);
511     if (retval)
512         goto cleanup;
513     locked = TRUE;
514 
515     if (ulog->kdb_hmagic != KDB_ULOG_HDR_MAGIC) {
516         if (ulog->kdb_hmagic != 0) {
517             retval = KRB5_LOG_CORRUPT;
518             goto cleanup;
519         }
520         reset_ulog(log_ctx);
521     }
522 
523     /* Reinit ulog if ulogentries changed such that we have too many entries or
524      * our first or last entry was written to the wrong location. */
525     if (ulog->kdb_num != 0 &&
526         (ulog->kdb_num > ulogentries ||
527          !check_sno(log_ctx, ulog->kdb_first_sno, &ulog->kdb_first_time) ||
528          !check_sno(log_ctx, ulog->kdb_last_sno, &ulog->kdb_last_time)))
529         reset_ulog(log_ctx);
530 
531     if (ulog->kdb_num != ulogentries) {
532         /* Expand the ulog file if it isn't big enough. */
533         filesize = sizeof(kdb_hlog_t) + ulogentries * ulog->kdb_block;
534         retval = extend_file_to(log_ctx->ulogfd, filesize);
535         if (retval)
536             goto cleanup;
537     }
538 
539 cleanup:
540     if (locked)
541         unlock_ulog(context);
542     if (retval)
543         ulog_fini(context);
544     return retval;
545 }
546 
547 /* Get the last set of updates seen, (last+1) to n is returned. */
548 krb5_error_code
ulog_get_entries(krb5_context context,const kdb_last_t * last,kdb_incr_result_t * ulog_handle)549 ulog_get_entries(krb5_context context, const kdb_last_t *last,
550                  kdb_incr_result_t *ulog_handle)
551 {
552     XDR xdrs;
553     kdb_ent_header_t *indx_log;
554     kdb_incr_update_t *upd;
555     unsigned int indx, count;
556     uint32_t sno;
557     krb5_error_code retval;
558     kdb_log_context *log_ctx;
559     kdb_hlog_t *ulog = NULL;
560     uint32_t ulogentries;
561 
562     INIT_ULOG(context);
563     ulogentries = log_ctx->ulogentries;
564 
565     retval = lock_ulog(context, KRB5_LOCKMODE_SHARED);
566     if (retval)
567         return retval;
568 
569     /* If another process terminated mid-update, reset the ulog and force full
570      * resyncs. */
571     if (ulog->kdb_state != KDB_STABLE)
572         reset_ulog(log_ctx);
573 
574     ulog_handle->ret = get_sno_status(log_ctx, last);
575     if (ulog_handle->ret != UPDATE_OK)
576         goto cleanup;
577 
578     sno = last->last_sno;
579     count = ulog->kdb_last_sno - sno;
580     upd = calloc(count, sizeof(kdb_incr_update_t));
581     if (upd == NULL) {
582         ulog_handle->ret = UPDATE_ERROR;
583         retval = ENOMEM;
584         goto cleanup;
585     }
586     ulog_handle->updates.kdb_ulog_t_val = upd;
587 
588     for (; sno < ulog->kdb_last_sno; sno++) {
589         indx = sno % ulogentries;
590         indx_log = INDEX(ulog, indx);
591 
592         memset(upd, 0, sizeof(kdb_incr_update_t));
593         xdrmem_create(&xdrs, (char *)indx_log->entry_data,
594                       indx_log->kdb_entry_size, XDR_DECODE);
595         if (!xdr_kdb_incr_update_t(&xdrs, upd)) {
596             ulog_handle->ret = UPDATE_ERROR;
597             retval = KRB5_LOG_CONV;
598             goto cleanup;
599         }
600 
601         /* Mark commitment since we didn't want to decode and encode the incr
602          * update record the first time. */
603         upd->kdb_commit = indx_log->kdb_commit;
604         upd++;
605     }
606 
607     ulog_handle->updates.kdb_ulog_t_len = count;
608 
609     ulog_handle->lastentry.last_sno = ulog->kdb_last_sno;
610     ulog_handle->lastentry.last_time.seconds = ulog->kdb_last_time.seconds;
611     ulog_handle->lastentry.last_time.useconds = ulog->kdb_last_time.useconds;
612     ulog_handle->ret = UPDATE_OK;
613 
614 cleanup:
615     unlock_ulog(context);
616     return retval;
617 }
618 
619 krb5_error_code
ulog_set_role(krb5_context ctx,iprop_role role)620 ulog_set_role(krb5_context ctx, iprop_role role)
621 {
622     if (create_log_context(ctx) == NULL)
623         return ENOMEM;
624     ctx->kdblog_context->iproprole = role;
625     return 0;
626 }
627 
628 update_status_t
ulog_get_sno_status(krb5_context context,const kdb_last_t * last)629 ulog_get_sno_status(krb5_context context, const kdb_last_t *last)
630 {
631     update_status_t status;
632 
633     if (lock_ulog(context, KRB5_LOCKMODE_SHARED) != 0)
634         return UPDATE_ERROR;
635     status = get_sno_status(context->kdblog_context, last);
636     unlock_ulog(context);
637     return status;
638 }
639 
640 krb5_error_code
ulog_get_last(krb5_context context,kdb_last_t * last_out)641 ulog_get_last(krb5_context context, kdb_last_t *last_out)
642 {
643     krb5_error_code ret;
644     kdb_log_context *log_ctx;
645     kdb_hlog_t *ulog;
646 
647     INIT_ULOG(context);
648     ret = lock_ulog(context, KRB5_LOCKMODE_SHARED);
649     if (ret)
650         return ret;
651     last_out->last_sno = log_ctx->ulog->kdb_last_sno;
652     last_out->last_time = log_ctx->ulog->kdb_last_time;
653     unlock_ulog(context);
654     return 0;
655 }
656 
657 krb5_error_code
ulog_set_last(krb5_context context,const kdb_last_t * last)658 ulog_set_last(krb5_context context, const kdb_last_t *last)
659 {
660     krb5_error_code ret;
661     kdb_log_context *log_ctx;
662     kdb_hlog_t *ulog;
663 
664     INIT_ULOG(context);
665     ret = lock_ulog(context, KRB5_LOCKMODE_EXCLUSIVE);
666     if (ret)
667         return ret;
668 
669     set_dummy(log_ctx, last->last_sno, &last->last_time);
670     sync_header(ulog);
671     unlock_ulog(context);
672     return 0;
673 }
674 
675 void
ulog_fini(krb5_context context)676 ulog_fini(krb5_context context)
677 {
678     kdb_log_context *log_ctx = context->kdblog_context;
679 
680     if (log_ctx == NULL)
681         return;
682     if (log_ctx->ulog != NULL)
683         munmap(log_ctx->ulog, MAXLOGLEN);
684     if (log_ctx->ulogfd != -1)
685         close(log_ctx->ulogfd);
686     free(log_ctx);
687     context->kdblog_context = NULL;
688 }
689