2 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
18 * This modules includes all the necessary functions that create and
19 * modify the Kerberos principal update and header logs.
22 #define getpagesize() sysconf(_SC_PAGESIZE)
24 static int pagesize
= 0;
26 #define INIT_ULOG(ctx) log_ctx = ctx->kdblog_context; \
29 * Sync update entry to disk.
32 ulog_sync_update(kdb_hlog_t
*ulog
, kdb_ent_header_t
*upd
)
34 ulong_t start
, end
, size
;
35 krb5_error_code retval
;
38 return (KRB5_LOG_ERROR
);
41 pagesize
= getpagesize();
43 start
= ((ulong_t
)upd
) & (~(pagesize
-1));
45 end
= (((ulong_t
)upd
) + ulog
->kdb_block
+
46 (pagesize
-1)) & (~(pagesize
-1));
49 if (retval
= msync((caddr_t
)start
, size
, MS_SYNC
)) {
57 * Sync memory to disk for the update log header.
60 ulog_sync_header(kdb_hlog_t
*ulog
)
64 pagesize
= getpagesize();
66 if (msync((caddr_t
)ulog
, pagesize
, MS_SYNC
)) {
68 * Couldn't sync to disk, let's panic
70 syslog(LOG_ERR
, "ulog_sync_header: could not sync to disk");
76 * Resizes the array elements. We reinitialize the update log rather than
77 * unrolling the the log and copying it over to a temporary log for obvious
78 * performance reasons. Slaves will subsequently do a full resync, but
79 * the need for resizing should be very small.
82 ulog_resize(kdb_hlog_t
*ulog
, uint32_t ulogentries
, int ulogfd
, uint_t recsize
)
84 uint_t new_block
, new_size
;
87 return (KRB5_LOG_ERROR
);
89 new_size
= sizeof (kdb_hlog_t
);
91 new_block
= (recsize
/ ULOG_BLOCK
) + 1;
92 new_block
*= ULOG_BLOCK
;
94 new_size
+= ulogentries
* new_block
;
96 if (new_size
<= MAXLOGLEN
) {
98 * Reinit log with new block size
100 (void) memset(ulog
, 0, sizeof (kdb_hlog_t
));
102 ulog
->kdb_hmagic
= KDB_HMAGIC
;
103 ulog
->db_version_num
= KDB_VERSION
;
104 ulog
->kdb_state
= KDB_STABLE
;
105 ulog
->kdb_block
= new_block
;
107 ulog_sync_header(ulog
);
110 * Time to expand log considering new block size
112 if (lseek(ulogfd
, new_size
, SEEK_SET
) == -1) {
116 if (write(ulogfd
, "+", 1) != 1) {
121 * Can't map into file larger than MAXLOGLEN
123 return (KRB5_LOG_ERROR
);
130 * Adds an entry to the update log.
131 * The layout of the update log looks like:
133 * header log -> [ update header -> xdr(kdb_incr_update_t) ], ...
136 ulog_add_update(krb5_context context
, kdb_incr_update_t
*upd
)
140 struct timeval timestamp
;
141 kdb_ent_header_t
*indx_log
;
144 krb5_error_code retval
;
146 kdb_log_context
*log_ctx
;
147 kdb_hlog_t
*ulog
= NULL
;
148 uint32_t ulogentries
;
152 ulogentries
= log_ctx
->ulogentries
;
153 ulogfd
= log_ctx
->ulogfd
;
156 return (KRB5_LOG_ERROR
);
158 (void) gettimeofday(×tamp
, NULL
);
159 ktime
.seconds
= timestamp
.tv_sec
;
160 ktime
.useconds
= timestamp
.tv_usec
;
162 upd_size
= xdr_sizeof((xdrproc_t
)xdr_kdb_incr_update_t
, upd
);
164 recsize
= sizeof (kdb_ent_header_t
) + upd_size
;
166 if (recsize
> ulog
->kdb_block
) {
167 if (retval
= ulog_resize(ulog
, ulogentries
, ulogfd
, recsize
)) {
168 /* Resize element array failed */
173 cur_sno
= ulog
->kdb_last_sno
;
176 * We need to overflow our sno, replicas will do full
177 * resyncs once they see their sno > than the masters.
179 if (cur_sno
== ULONG_MAX
)
185 * We squirrel this away for finish_update() to index
187 upd
->kdb_entry_sno
= cur_sno
;
189 i
= (cur_sno
- 1) % ulogentries
;
191 indx_log
= (kdb_ent_header_t
*)INDEX(ulog
, i
);
193 (void) memset(indx_log
, 0, ulog
->kdb_block
);
195 indx_log
->kdb_umagic
= KDB_UMAGIC
;
196 indx_log
->kdb_entry_size
= upd_size
;
197 indx_log
->kdb_entry_sno
= cur_sno
;
198 indx_log
->kdb_time
= upd
->kdb_time
= ktime
;
199 indx_log
->kdb_commit
= upd
->kdb_commit
= FALSE
;
201 ulog
->kdb_state
= KDB_UNSTABLE
;
203 xdrmem_create(&xdrs
, (char *)indx_log
->entry_data
,
204 indx_log
->kdb_entry_size
, XDR_ENCODE
);
205 if (!xdr_kdb_incr_update_t(&xdrs
, upd
))
206 return (KRB5_LOG_CONV
);
208 if (retval
= ulog_sync_update(ulog
, indx_log
))
211 if (ulog
->kdb_num
< ulogentries
)
214 ulog
->kdb_last_sno
= cur_sno
;
215 ulog
->kdb_last_time
= ktime
;
218 * Since this is a circular array, once we circled, kdb_first_sno is
219 * always kdb_entry_sno + 1.
221 if (cur_sno
> ulogentries
) {
222 i
= upd
->kdb_entry_sno
% ulogentries
;
223 indx_log
= (kdb_ent_header_t
*)INDEX(ulog
, i
);
224 ulog
->kdb_first_sno
= indx_log
->kdb_entry_sno
;
225 ulog
->kdb_first_time
= indx_log
->kdb_time
;
226 } else if (cur_sno
== 1) {
227 ulog
->kdb_first_sno
= 1;
228 ulog
->kdb_first_time
= indx_log
->kdb_time
;
231 ulog_sync_header(ulog
);
237 * Mark the log entry as committed and sync the memory mapped log
241 ulog_finish_update(krb5_context context
, kdb_incr_update_t
*upd
)
243 krb5_error_code retval
;
244 kdb_ent_header_t
*indx_log
;
246 kdb_log_context
*log_ctx
;
247 kdb_hlog_t
*ulog
= NULL
;
248 uint32_t ulogentries
;
251 ulogentries
= log_ctx
->ulogentries
;
253 i
= (upd
->kdb_entry_sno
- 1) % ulogentries
;
255 indx_log
= (kdb_ent_header_t
*)INDEX(ulog
, i
);
257 indx_log
->kdb_commit
= TRUE
;
259 ulog
->kdb_state
= KDB_STABLE
;
261 if (retval
= ulog_sync_update(ulog
, indx_log
))
264 ulog_sync_header(ulog
);
270 * Set the header log details on the slave and sync it to file.
273 ulog_finish_update_slave(kdb_hlog_t
*ulog
, kdb_last_t lastentry
)
276 ulog
->kdb_last_sno
= lastentry
.last_sno
;
277 ulog
->kdb_last_time
= lastentry
.last_time
;
279 ulog_sync_header(ulog
);
283 * Delete an entry to the update log.
286 ulog_delete_update(krb5_context context
, kdb_incr_update_t
*upd
)
289 upd
->kdb_deleted
= TRUE
;
291 return (ulog_add_update(context
, upd
));
295 * Used by the slave or master (during ulog_check) to update it's hash db from
296 * the incr update log.
299 ulog_replay(krb5_context context
, kdb_incr_result_t
*incr_ret
)
301 krb5_db_entry
*entry
= NULL
;
302 kdb_incr_update_t
*upd
= NULL
, *fupd
;
303 int i
, no_of_updates
;
304 krb5_error_code retval
;
305 krb5_principal dbprinc
= NULL
;
307 char *dbprincstr
= NULL
;
308 kdb_log_context
*log_ctx
;
309 kdb_hlog_t
*ulog
= NULL
;
314 no_of_updates
= incr_ret
->updates
.kdb_ulog_t_len
;
315 upd
= incr_ret
->updates
.kdb_ulog_t_val
;
319 * We reset last_sno and last_time to 0, if krb5_db_put_principal
320 * or krb5_db_delete_principal fail.
322 errlast
.last_sno
= (unsigned int)0;
323 errlast
.last_time
.seconds
= (unsigned int)0;
324 errlast
.last_time
.useconds
= (unsigned int)0;
326 if (krb5_db_inited(context
)) {
327 retval
= krb5_db_open(context
, NULL
,
328 KRB5_KDB_OPEN_RW
| KRB5_KDB_SRV_TYPE_ADMIN
);
334 for (i
= 0; i
< no_of_updates
; i
++) {
337 if (!upd
->kdb_commit
)
340 if (upd
->kdb_deleted
) {
341 dbprincstr
= malloc((upd
->kdb_princ_name
.utf8str_t_len
342 + 1) * sizeof (char));
344 if (dbprincstr
== NULL
) {
349 (void) strlcpy(dbprincstr
,
350 (char *)upd
->kdb_princ_name
.utf8str_t_val
,
351 (upd
->kdb_princ_name
.utf8str_t_len
+ 1));
353 if (retval
= krb5_parse_name(context
, dbprincstr
,
360 retval
= krb5_db_delete_principal(context
,
364 krb5_free_principal(context
, dbprinc
);
369 entry
= (krb5_db_entry
*)malloc(sizeof (krb5_db_entry
));
376 (void) memset(entry
, 0, sizeof (krb5_db_entry
));
378 if (retval
= ulog_conv_2dbentry(context
, entry
, upd
, 1))
381 retval
= krb5_db_put_principal(context
, entry
,
385 krb5_db_free_principal(context
, entry
, nentry
);
398 ulog_free_entries(fupd
, no_of_updates
);
400 if (log_ctx
&& (log_ctx
->iproprole
== IPROP_SLAVE
)) {
402 ulog_finish_update_slave(ulog
, errlast
);
404 ulog_finish_update_slave(ulog
, incr_ret
->lastentry
);
408 krb5_db_fini(context
);
414 * Validate the log file and resync any uncommitted update entries
415 * to the principal database.
418 ulog_check(krb5_context context
, kdb_hlog_t
*ulog
)
421 krb5_error_code retval
= 0;
423 kdb_ent_header_t
*indx_log
;
424 kdb_incr_update_t
*upd
= NULL
;
425 kdb_incr_result_t
*incr_ret
= NULL
;
427 ulog
->kdb_state
= KDB_STABLE
;
429 for (i
= 0; i
< ulog
->kdb_num
; i
++) {
430 indx_log
= (kdb_ent_header_t
*)INDEX(ulog
, i
);
432 if (indx_log
->kdb_umagic
!= KDB_UMAGIC
) {
434 * Update entry corrupted we should scream and die
436 ulog
->kdb_state
= KDB_CORRUPT
;
437 retval
= KRB5_LOG_CORRUPT
;
441 if (indx_log
->kdb_commit
== FALSE
) {
442 ulog
->kdb_state
= KDB_UNSTABLE
;
444 incr_ret
= (kdb_incr_result_t
*)
445 malloc(sizeof (kdb_incr_result_t
));
446 if (incr_ret
== NULL
) {
451 upd
= (kdb_incr_update_t
*)
452 malloc(sizeof (kdb_incr_update_t
));
458 (void) memset(upd
, 0, sizeof (kdb_incr_update_t
));
459 xdrmem_create(&xdrs
, (char *)indx_log
->entry_data
,
460 indx_log
->kdb_entry_size
, XDR_DECODE
);
461 if (!xdr_kdb_incr_update_t(&xdrs
, upd
)) {
462 retval
= KRB5_LOG_CONV
;
466 incr_ret
->updates
.kdb_ulog_t_len
= 1;
467 incr_ret
->updates
.kdb_ulog_t_val
= upd
;
469 upd
->kdb_commit
= TRUE
;
472 * We don't want to readd this update and just use the
473 * existing update to be propagated later on
475 ulog_set_role(context
, IPROP_NULL
);
476 retval
= ulog_replay(context
, incr_ret
);
479 * upd was freed by ulog_replay, we NULL
480 * the pointer in case we subsequently break from loop.
487 ulog_set_role(context
, IPROP_MASTER
);
493 * We flag this as committed since this was
494 * the last entry before kadmind crashed, ergo
495 * the slaves have not seen this update before
497 indx_log
->kdb_commit
= TRUE
;
498 retval
= ulog_sync_update(ulog
, indx_log
);
502 ulog
->kdb_state
= KDB_STABLE
;
508 ulog_free_entries(upd
, 1);
512 ulog_sync_header(ulog
);
518 * Map the log file to memory for performance and simplicity.
520 * Called by: if iprop_enabled then ulog_map();
521 * Assumes that the caller will terminate on ulog_map, hence munmap and
522 * closing of the fd are implicitly performed by the caller.
523 * Returns 0 on success else failure.
526 ulog_map(krb5_context context
, kadm5_config_params
*params
, int caller
)
529 krb5_error_code retval
;
530 uint32_t ulog_filesize
;
531 char logname
[MAX_FILENAME
];
532 kdb_log_context
*log_ctx
;
533 kdb_hlog_t
*ulog
= NULL
;
534 uint32_t ulogentries
;
537 if ((caller
== FKADMIND
) || (caller
== FKCOMMAND
))
538 ulogentries
= params
->iprop_ulogsize
;
540 ulog_filesize
= sizeof (kdb_hlog_t
);
542 if (strlcpy(logname
, params
->dbname
, MAX_FILENAME
) >= MAX_FILENAME
)
543 return (KRB5_LOG_ERROR
);
544 if (strlcat(logname
, ".ulog", MAX_FILENAME
) >= MAX_FILENAME
)
545 return (KRB5_LOG_ERROR
);
547 if (stat(logname
, &st
) == -1) {
549 if (caller
== FKPROPLOG
) {
551 * File doesn't exist so we exit with kproplog
556 if ((ulogfd
= open(logname
, O_RDWR
+O_CREAT
, 0600)) == -1) {
560 if (lseek(ulogfd
, 0L, SEEK_CUR
) == -1) {
564 if ((caller
== FKADMIND
) || (caller
== FKCOMMAND
))
565 ulog_filesize
+= ulogentries
* ULOG_BLOCK
;
567 if (lseek(ulogfd
, ulog_filesize
, SEEK_SET
) == -1) {
571 if (write(ulogfd
, "+", 1) != 1) {
577 if ((ulogfd
= open(logname
, O_RDWR
, 0600)) == -1) {
579 * Can't open existing log file
585 if (caller
== FKPROPLOG
) {
587 ulog_filesize
= st
.st_size
;
589 ulog
= mmap(NULL
, ulog_filesize
,
590 PROT_READ
+PROT_WRITE
, MAP_PRIVATE
, ulogfd
, 0);
593 * else kadmind, kpropd, & kcommands should udpate stores
595 ulog
= mmap(NULL
, MAXLOGLEN
,
596 PROT_READ
+PROT_WRITE
, MAP_SHARED
, ulogfd
, 0);
599 if ((int)(ulog
) == -1) {
601 * Can't map update log file to memory
606 if (!context
->kdblog_context
) {
607 if (!(log_ctx
= malloc(sizeof (kdb_log_context
))))
609 context
->kdblog_context
= (void *)log_ctx
;
611 log_ctx
= context
->kdblog_context
;
612 log_ctx
->ulog
= ulog
;
613 log_ctx
->ulogentries
= ulogentries
;
614 log_ctx
->ulogfd
= ulogfd
;
616 if (ulog
->kdb_hmagic
!= KDB_HMAGIC
) {
617 if (ulog
->kdb_hmagic
== 0) {
621 (void) memset(ulog
, 0, sizeof (kdb_hlog_t
));
623 ulog
->kdb_hmagic
= KDB_HMAGIC
;
624 ulog
->db_version_num
= KDB_VERSION
;
625 ulog
->kdb_state
= KDB_STABLE
;
626 ulog
->kdb_block
= ULOG_BLOCK
;
627 if (!(caller
== FKPROPLOG
))
628 ulog_sync_header(ulog
);
630 return (KRB5_LOG_CORRUPT
);
634 if (caller
== FKADMIND
) {
635 switch (ulog
->kdb_state
) {
639 * Log is currently un/stable, check anyway
641 retval
= ulog_check(context
, ulog
);
642 if (retval
== KRB5_LOG_CORRUPT
) {
647 return (KRB5_LOG_CORRUPT
);
652 return (KRB5_LOG_ERROR
);
654 } else if ((caller
== FKPROPLOG
) || (caller
== FKPROPD
)) {
656 * kproplog and kpropd don't need to do anything else
662 * Reinit ulog if the log is being truncated or expanded after
665 if (ulog
->kdb_num
!= ulogentries
) {
666 if ((ulog
->kdb_num
!= 0) &&
667 ((ulog
->kdb_last_sno
> ulog
->kdb_num
) ||
668 (ulog
->kdb_num
> ulogentries
))) {
669 (void) memset(ulog
, 0, sizeof (kdb_hlog_t
));
671 ulog
->kdb_hmagic
= KDB_HMAGIC
;
672 ulog
->db_version_num
= KDB_VERSION
;
673 ulog
->kdb_state
= KDB_STABLE
;
674 ulog
->kdb_block
= ULOG_BLOCK
;
676 ulog_sync_header(ulog
);
680 * Expand ulog if we have specified a greater size
682 if (ulog
->kdb_num
< ulogentries
) {
683 ulog_filesize
+= ulogentries
* ulog
->kdb_block
;
685 if (lseek(ulogfd
, ulog_filesize
, SEEK_SET
) == -1) {
689 if (write(ulogfd
, "+", 1) != 1) {
699 * Get the last set of updates seen, (last+1) to n is returned.
703 krb5_context context
, /* input - krb5 lib config */
704 kdb_last_t last
, /* input - slave's last sno */
705 kdb_incr_result_t
*ulog_handle
) /* output - incr result for slave */
708 kdb_ent_header_t
*indx_log
;
709 kdb_incr_update_t
*upd
;
710 uint_t indx
, count
, tdiff
;
712 krb5_error_code retval
;
713 struct timeval timestamp
;
714 kdb_log_context
*log_ctx
;
715 kdb_hlog_t
*ulog
= NULL
;
716 uint32_t ulogentries
;
719 ulogentries
= log_ctx
->ulogentries
;
722 * Check to make sure we don't have a corrupt ulog first.
724 if (ulog
->kdb_state
== KDB_CORRUPT
) {
725 ulog_handle
->ret
= UPDATE_ERROR
;
726 return (KRB5_LOG_CORRUPT
);
729 gettimeofday(×tamp
, NULL
);
731 tdiff
= timestamp
.tv_sec
- ulog
->kdb_last_time
.seconds
;
732 if (tdiff
<= ULOG_IDLE_TIME
) {
733 ulog_handle
->ret
= UPDATE_BUSY
;
738 * We need to lock out other processes here, such as kadmin.local,
739 * since we are looking at the last_sno and looking up updates. So
740 * we can share with other readers.
742 retval
= krb5_db_lock(context
, KRB5_LOCKMODE_SHARED
);
747 * We may have overflowed the update log or we shrunk the log, or
748 * the client's ulog has just been created.
750 if ((last
.last_sno
> ulog
->kdb_last_sno
) ||
751 (last
.last_sno
< ulog
->kdb_first_sno
) ||
752 (last
.last_sno
== 0)) {
753 ulog_handle
->lastentry
.last_sno
= ulog
->kdb_last_sno
;
754 (void) krb5_db_unlock(context
);
755 ulog_handle
->ret
= UPDATE_FULL_RESYNC_NEEDED
;
757 } else if (last
.last_sno
<= ulog
->kdb_last_sno
) {
760 indx
= (sno
- 1) % ulogentries
;
762 indx_log
= (kdb_ent_header_t
*)INDEX(ulog
, indx
);
765 * Validate the time stamp just to make sure it was the same sno
767 if ((indx_log
->kdb_time
.seconds
== last
.last_time
.seconds
) &&
768 (indx_log
->kdb_time
.useconds
== last
.last_time
.useconds
)) {
771 * If we have the same sno we return success
773 if (last
.last_sno
== ulog
->kdb_last_sno
) {
774 (void) krb5_db_unlock(context
);
775 ulog_handle
->ret
= UPDATE_NIL
;
779 count
= ulog
->kdb_last_sno
- sno
;
781 ulog_handle
->updates
.kdb_ulog_t_val
=
782 (kdb_incr_update_t
*)malloc(
783 sizeof (kdb_incr_update_t
) * count
);
785 upd
= ulog_handle
->updates
.kdb_ulog_t_val
;
788 (void) krb5_db_unlock(context
);
789 ulog_handle
->ret
= UPDATE_ERROR
;
793 while (sno
< ulog
->kdb_last_sno
) {
794 indx
= sno
% ulogentries
;
796 indx_log
= (kdb_ent_header_t
*)
799 (void) memset(upd
, 0,
800 sizeof (kdb_incr_update_t
));
802 (char *)indx_log
->entry_data
,
803 indx_log
->kdb_entry_size
, XDR_DECODE
);
804 if (!xdr_kdb_incr_update_t(&xdrs
, upd
)) {
805 (void) krb5_db_unlock(context
);
806 ulog_handle
->ret
= UPDATE_ERROR
;
807 return (KRB5_LOG_CONV
);
810 * Mark commitment since we didn't
811 * want to decode and encode the
812 * incr update record the first time.
814 upd
->kdb_commit
= indx_log
->kdb_commit
;
820 ulog_handle
->updates
.kdb_ulog_t_len
= count
;
822 ulog_handle
->lastentry
.last_sno
= ulog
->kdb_last_sno
;
823 ulog_handle
->lastentry
.last_time
.seconds
=
824 ulog
->kdb_last_time
.seconds
;
825 ulog_handle
->lastentry
.last_time
.useconds
=
826 ulog
->kdb_last_time
.useconds
;
827 ulog_handle
->ret
= UPDATE_OK
;
829 (void) krb5_db_unlock(context
);
834 * We have time stamp mismatch or we no longer have
835 * the slave's last sno, so we brute force it
837 (void) krb5_db_unlock(context
);
838 ulog_handle
->ret
= UPDATE_FULL_RESYNC_NEEDED
;
845 * Should never get here, return error
847 ulog_handle
->ret
= UPDATE_ERROR
;
848 return (KRB5_LOG_ERROR
);
852 ulog_set_role(krb5_context ctx
, iprop_role role
)
854 kdb_log_context
*log_ctx
;
856 if (!ctx
->kdblog_context
) {
857 if (!(log_ctx
= malloc(sizeof (kdb_log_context
))))
859 ctx
->kdblog_context
= (void *)log_ctx
;
861 log_ctx
= ctx
->kdblog_context
;
863 log_ctx
->iproprole
= role
;