2 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
6 #pragma ident "%Z%%M% %I% %E% SMI"
20 * This modules includes all the necessary functions that create and
21 * modify the Kerberos principal update and header logs.
24 #define getpagesize() sysconf(_SC_PAGESIZE)
26 static int pagesize
= 0;
28 #define INIT_ULOG(ctx) log_ctx = ctx->kdblog_context; \
31 * Sync update entry to disk.
34 ulog_sync_update(kdb_hlog_t
*ulog
, kdb_ent_header_t
*upd
)
36 ulong_t start
, end
, size
;
37 krb5_error_code retval
;
40 return (KRB5_LOG_ERROR
);
43 pagesize
= getpagesize();
45 start
= ((ulong_t
)upd
) & (~(pagesize
-1));
47 end
= (((ulong_t
)upd
) + ulog
->kdb_block
+
48 (pagesize
-1)) & (~(pagesize
-1));
51 if (retval
= msync((caddr_t
)start
, size
, MS_SYNC
)) {
59 * Sync memory to disk for the update log header.
62 ulog_sync_header(kdb_hlog_t
*ulog
)
66 pagesize
= getpagesize();
68 if (msync((caddr_t
)ulog
, pagesize
, MS_SYNC
)) {
70 * Couldn't sync to disk, let's panic
72 syslog(LOG_ERR
, "ulog_sync_header: could not sync to disk");
78 * Resizes the array elements. We reinitialize the update log rather than
79 * unrolling the the log and copying it over to a temporary log for obvious
80 * performance reasons. Slaves will subsequently do a full resync, but
81 * the need for resizing should be very small.
84 ulog_resize(kdb_hlog_t
*ulog
, uint32_t ulogentries
, int ulogfd
, uint_t recsize
)
86 uint_t new_block
, new_size
;
89 return (KRB5_LOG_ERROR
);
91 new_size
= sizeof (kdb_hlog_t
);
93 new_block
= (recsize
/ ULOG_BLOCK
) + 1;
94 new_block
*= ULOG_BLOCK
;
96 new_size
+= ulogentries
* new_block
;
98 if (new_size
<= MAXLOGLEN
) {
100 * Reinit log with new block size
102 (void) memset(ulog
, 0, sizeof (kdb_hlog_t
));
104 ulog
->kdb_hmagic
= KDB_HMAGIC
;
105 ulog
->db_version_num
= KDB_VERSION
;
106 ulog
->kdb_state
= KDB_STABLE
;
107 ulog
->kdb_block
= new_block
;
109 ulog_sync_header(ulog
);
112 * Time to expand log considering new block size
114 if (lseek(ulogfd
, new_size
, SEEK_SET
) == -1) {
118 if (write(ulogfd
, "+", 1) != 1) {
123 * Can't map into file larger than MAXLOGLEN
125 return (KRB5_LOG_ERROR
);
132 * Adds an entry to the update log.
133 * The layout of the update log looks like:
135 * header log -> [ update header -> xdr(kdb_incr_update_t) ], ...
138 ulog_add_update(krb5_context context
, kdb_incr_update_t
*upd
)
142 struct timeval timestamp
;
143 kdb_ent_header_t
*indx_log
;
146 krb5_error_code retval
;
148 kdb_log_context
*log_ctx
;
149 kdb_hlog_t
*ulog
= NULL
;
150 uint32_t ulogentries
;
154 ulogentries
= log_ctx
->ulogentries
;
155 ulogfd
= log_ctx
->ulogfd
;
158 return (KRB5_LOG_ERROR
);
160 (void) gettimeofday(×tamp
, NULL
);
161 ktime
.seconds
= timestamp
.tv_sec
;
162 ktime
.useconds
= timestamp
.tv_usec
;
164 upd_size
= xdr_sizeof((xdrproc_t
)xdr_kdb_incr_update_t
, upd
);
166 recsize
= sizeof (kdb_ent_header_t
) + upd_size
;
168 if (recsize
> ulog
->kdb_block
) {
169 if (retval
= ulog_resize(ulog
, ulogentries
, ulogfd
, recsize
)) {
170 /* Resize element array failed */
175 cur_sno
= ulog
->kdb_last_sno
;
178 * We need to overflow our sno, replicas will do full
179 * resyncs once they see their sno > than the masters.
181 if (cur_sno
== ULONG_MAX
)
187 * We squirrel this away for finish_update() to index
189 upd
->kdb_entry_sno
= cur_sno
;
191 i
= (cur_sno
- 1) % ulogentries
;
193 indx_log
= (kdb_ent_header_t
*)INDEX(ulog
, i
);
195 (void) memset(indx_log
, 0, ulog
->kdb_block
);
197 indx_log
->kdb_umagic
= KDB_UMAGIC
;
198 indx_log
->kdb_entry_size
= upd_size
;
199 indx_log
->kdb_entry_sno
= cur_sno
;
200 indx_log
->kdb_time
= upd
->kdb_time
= ktime
;
201 indx_log
->kdb_commit
= upd
->kdb_commit
= FALSE
;
203 ulog
->kdb_state
= KDB_UNSTABLE
;
205 xdrmem_create(&xdrs
, (char *)indx_log
->entry_data
,
206 indx_log
->kdb_entry_size
, XDR_ENCODE
);
207 if (!xdr_kdb_incr_update_t(&xdrs
, upd
))
208 return (KRB5_LOG_CONV
);
210 if (retval
= ulog_sync_update(ulog
, indx_log
))
213 if (ulog
->kdb_num
< ulogentries
)
216 ulog
->kdb_last_sno
= cur_sno
;
217 ulog
->kdb_last_time
= ktime
;
220 * Since this is a circular array, once we circled, kdb_first_sno is
221 * always kdb_entry_sno + 1.
223 if (cur_sno
> ulogentries
) {
224 i
= upd
->kdb_entry_sno
% ulogentries
;
225 indx_log
= (kdb_ent_header_t
*)INDEX(ulog
, i
);
226 ulog
->kdb_first_sno
= indx_log
->kdb_entry_sno
;
227 ulog
->kdb_first_time
= indx_log
->kdb_time
;
228 } else if (cur_sno
== 1) {
229 ulog
->kdb_first_sno
= 1;
230 ulog
->kdb_first_time
= indx_log
->kdb_time
;
233 ulog_sync_header(ulog
);
239 * Mark the log entry as committed and sync the memory mapped log
243 ulog_finish_update(krb5_context context
, kdb_incr_update_t
*upd
)
245 krb5_error_code retval
;
246 kdb_ent_header_t
*indx_log
;
248 kdb_log_context
*log_ctx
;
249 kdb_hlog_t
*ulog
= NULL
;
250 uint32_t ulogentries
;
253 ulogentries
= log_ctx
->ulogentries
;
255 i
= (upd
->kdb_entry_sno
- 1) % ulogentries
;
257 indx_log
= (kdb_ent_header_t
*)INDEX(ulog
, i
);
259 indx_log
->kdb_commit
= TRUE
;
261 ulog
->kdb_state
= KDB_STABLE
;
263 if (retval
= ulog_sync_update(ulog
, indx_log
))
266 ulog_sync_header(ulog
);
272 * Set the header log details on the slave and sync it to file.
275 ulog_finish_update_slave(kdb_hlog_t
*ulog
, kdb_last_t lastentry
)
278 ulog
->kdb_last_sno
= lastentry
.last_sno
;
279 ulog
->kdb_last_time
= lastentry
.last_time
;
281 ulog_sync_header(ulog
);
285 * Delete an entry to the update log.
288 ulog_delete_update(krb5_context context
, kdb_incr_update_t
*upd
)
291 upd
->kdb_deleted
= TRUE
;
293 return (ulog_add_update(context
, upd
));
297 * Used by the slave or master (during ulog_check) to update it's hash db from
298 * the incr update log.
301 ulog_replay(krb5_context context
, kdb_incr_result_t
*incr_ret
)
303 krb5_db_entry
*entry
= NULL
;
304 kdb_incr_update_t
*upd
= NULL
, *fupd
;
305 int i
, no_of_updates
;
306 krb5_error_code retval
;
307 krb5_principal dbprinc
= NULL
;
309 char *dbprincstr
= NULL
;
310 kdb_log_context
*log_ctx
;
311 kdb_hlog_t
*ulog
= NULL
;
316 no_of_updates
= incr_ret
->updates
.kdb_ulog_t_len
;
317 upd
= incr_ret
->updates
.kdb_ulog_t_val
;
321 * We reset last_sno and last_time to 0, if krb5_db_put_principal
322 * or krb5_db_delete_principal fail.
324 errlast
.last_sno
= (unsigned int)0;
325 errlast
.last_time
.seconds
= (unsigned int)0;
326 errlast
.last_time
.useconds
= (unsigned int)0;
328 if (krb5_db_inited(context
)) {
329 retval
= krb5_db_open(context
, NULL
,
330 KRB5_KDB_OPEN_RW
| KRB5_KDB_SRV_TYPE_ADMIN
);
336 for (i
= 0; i
< no_of_updates
; i
++) {
339 if (!upd
->kdb_commit
)
342 if (upd
->kdb_deleted
) {
343 dbprincstr
= malloc((upd
->kdb_princ_name
.utf8str_t_len
344 + 1) * sizeof (char));
346 if (dbprincstr
== NULL
) {
351 (void) strlcpy(dbprincstr
,
352 (char *)upd
->kdb_princ_name
.utf8str_t_val
,
353 (upd
->kdb_princ_name
.utf8str_t_len
+ 1));
355 if (retval
= krb5_parse_name(context
, dbprincstr
,
363 retval
= krb5_db_delete_principal(context
,
367 krb5_free_principal(context
, dbprinc
);
372 entry
= (krb5_db_entry
*)malloc(sizeof (krb5_db_entry
));
379 (void) memset(entry
, 0, sizeof (krb5_db_entry
));
381 if (retval
= ulog_conv_2dbentry(context
, entry
, upd
, 1))
384 retval
= krb5_db_put_principal(context
, entry
,
388 krb5_db_free_principal(context
, entry
, nentry
);
401 ulog_free_entries(fupd
, no_of_updates
);
403 if (log_ctx
&& (log_ctx
->iproprole
== IPROP_SLAVE
)) {
405 ulog_finish_update_slave(ulog
, errlast
);
407 ulog_finish_update_slave(ulog
, incr_ret
->lastentry
);
411 krb5_db_fini(context
);
417 * Validate the log file and resync any uncommitted update entries
418 * to the principal database.
421 ulog_check(krb5_context context
, kdb_hlog_t
*ulog
)
424 krb5_error_code retval
= 0;
426 kdb_ent_header_t
*indx_log
;
427 kdb_incr_update_t
*upd
= NULL
;
428 kdb_incr_result_t
*incr_ret
= NULL
;
430 ulog
->kdb_state
= KDB_STABLE
;
432 for (i
= 0; i
< ulog
->kdb_num
; i
++) {
433 indx_log
= (kdb_ent_header_t
*)INDEX(ulog
, i
);
435 if (indx_log
->kdb_umagic
!= KDB_UMAGIC
) {
437 * Update entry corrupted we should scream and die
439 ulog
->kdb_state
= KDB_CORRUPT
;
440 retval
= KRB5_LOG_CORRUPT
;
444 if (indx_log
->kdb_commit
== FALSE
) {
445 ulog
->kdb_state
= KDB_UNSTABLE
;
447 incr_ret
= (kdb_incr_result_t
*)
448 malloc(sizeof (kdb_incr_result_t
));
449 if (incr_ret
== NULL
) {
454 upd
= (kdb_incr_update_t
*)
455 malloc(sizeof (kdb_incr_update_t
));
461 (void) memset(upd
, 0, sizeof (kdb_incr_update_t
));
462 xdrmem_create(&xdrs
, (char *)indx_log
->entry_data
,
463 indx_log
->kdb_entry_size
, XDR_DECODE
);
464 if (!xdr_kdb_incr_update_t(&xdrs
, upd
)) {
465 retval
= KRB5_LOG_CONV
;
469 incr_ret
->updates
.kdb_ulog_t_len
= 1;
470 incr_ret
->updates
.kdb_ulog_t_val
= upd
;
472 upd
->kdb_commit
= TRUE
;
475 * We don't want to readd this update and just use the
476 * existing update to be propagated later on
478 ulog_set_role(context
, IPROP_NULL
);
479 retval
= ulog_replay(context
, incr_ret
);
482 * upd was freed by ulog_replay, we NULL
483 * the pointer in case we subsequently break from loop.
490 ulog_set_role(context
, IPROP_MASTER
);
496 * We flag this as committed since this was
497 * the last entry before kadmind crashed, ergo
498 * the slaves have not seen this update before
500 indx_log
->kdb_commit
= TRUE
;
501 retval
= ulog_sync_update(ulog
, indx_log
);
505 ulog
->kdb_state
= KDB_STABLE
;
511 ulog_free_entries(upd
, 1);
516 ulog_sync_header(ulog
);
522 * Map the log file to memory for performance and simplicity.
524 * Called by: if iprop_enabled then ulog_map();
525 * Assumes that the caller will terminate on ulog_map, hence munmap and
526 * closing of the fd are implicitly performed by the caller.
527 * Returns 0 on success else failure.
530 ulog_map(krb5_context context
, kadm5_config_params
*params
, int caller
)
533 krb5_error_code retval
;
534 uint32_t ulog_filesize
;
535 char logname
[MAX_FILENAME
];
536 kdb_log_context
*log_ctx
;
537 kdb_hlog_t
*ulog
= NULL
;
538 uint32_t ulogentries
;
541 if ((caller
== FKADMIND
) || (caller
== FKCOMMAND
))
542 ulogentries
= params
->iprop_ulogsize
;
544 ulog_filesize
= sizeof (kdb_hlog_t
);
546 if (strlcpy(logname
, params
->dbname
, MAX_FILENAME
) >= MAX_FILENAME
)
547 return (KRB5_LOG_ERROR
);
548 if (strlcat(logname
, ".ulog", MAX_FILENAME
) >= MAX_FILENAME
)
549 return (KRB5_LOG_ERROR
);
551 if (stat(logname
, &st
) == -1) {
553 if (caller
== FKPROPLOG
) {
555 * File doesn't exist so we exit with kproplog
560 if ((ulogfd
= open(logname
, O_RDWR
+O_CREAT
, 0600)) == -1) {
564 if (lseek(ulogfd
, 0L, SEEK_CUR
) == -1) {
568 if ((caller
== FKADMIND
) || (caller
== FKCOMMAND
))
569 ulog_filesize
+= ulogentries
* ULOG_BLOCK
;
571 if (lseek(ulogfd
, ulog_filesize
, SEEK_SET
) == -1) {
575 if (write(ulogfd
, "+", 1) != 1) {
581 if ((ulogfd
= open(logname
, O_RDWR
, 0600)) == -1) {
583 * Can't open existing log file
589 if (caller
== FKPROPLOG
) {
591 ulog_filesize
= st
.st_size
;
593 ulog
= (kdb_hlog_t
*)mmap(0, ulog_filesize
,
594 PROT_READ
+PROT_WRITE
, MAP_PRIVATE
, ulogfd
, 0);
597 * else kadmind, kpropd, & kcommands should udpate stores
599 ulog
= (kdb_hlog_t
*)mmap(0, MAXLOGLEN
,
600 PROT_READ
+PROT_WRITE
, MAP_SHARED
, ulogfd
, 0);
603 if ((int)(ulog
) == -1) {
605 * Can't map update log file to memory
610 if (!context
->kdblog_context
) {
611 if (!(log_ctx
= malloc(sizeof (kdb_log_context
))))
613 context
->kdblog_context
= (void *)log_ctx
;
615 log_ctx
= context
->kdblog_context
;
616 log_ctx
->ulog
= ulog
;
617 log_ctx
->ulogentries
= ulogentries
;
618 log_ctx
->ulogfd
= ulogfd
;
620 if (ulog
->kdb_hmagic
!= KDB_HMAGIC
) {
621 if (ulog
->kdb_hmagic
== 0) {
625 (void) memset(ulog
, 0, sizeof (kdb_hlog_t
));
627 ulog
->kdb_hmagic
= KDB_HMAGIC
;
628 ulog
->db_version_num
= KDB_VERSION
;
629 ulog
->kdb_state
= KDB_STABLE
;
630 ulog
->kdb_block
= ULOG_BLOCK
;
631 if (!(caller
== FKPROPLOG
))
632 ulog_sync_header(ulog
);
634 return (KRB5_LOG_CORRUPT
);
638 if (caller
== FKADMIND
) {
639 switch (ulog
->kdb_state
) {
643 * Log is currently un/stable, check anyway
645 retval
= ulog_check(context
, ulog
);
646 if (retval
== KRB5_LOG_CORRUPT
) {
651 return (KRB5_LOG_CORRUPT
);
656 return (KRB5_LOG_ERROR
);
658 } else if ((caller
== FKPROPLOG
) || (caller
== FKPROPD
)) {
660 * kproplog and kpropd don't need to do anything else
666 * Reinit ulog if the log is being truncated or expanded after
669 if (ulog
->kdb_num
!= ulogentries
) {
670 if ((ulog
->kdb_num
!= 0) &&
671 ((ulog
->kdb_last_sno
> ulog
->kdb_num
) ||
672 (ulog
->kdb_num
> ulogentries
))) {
673 (void) memset(ulog
, 0, sizeof (kdb_hlog_t
));
675 ulog
->kdb_hmagic
= KDB_HMAGIC
;
676 ulog
->db_version_num
= KDB_VERSION
;
677 ulog
->kdb_state
= KDB_STABLE
;
678 ulog
->kdb_block
= ULOG_BLOCK
;
680 ulog_sync_header(ulog
);
684 * Expand ulog if we have specified a greater size
686 if (ulog
->kdb_num
< ulogentries
) {
687 ulog_filesize
+= ulogentries
* ulog
->kdb_block
;
689 if (lseek(ulogfd
, ulog_filesize
, SEEK_SET
) == -1) {
693 if (write(ulogfd
, "+", 1) != 1) {
703 * Get the last set of updates seen, (last+1) to n is returned.
707 krb5_context context
, /* input - krb5 lib config */
708 kdb_last_t last
, /* input - slave's last sno */
709 kdb_incr_result_t
*ulog_handle
) /* output - incr result for slave */
712 kdb_ent_header_t
*indx_log
;
713 kdb_incr_update_t
*upd
;
714 uint_t indx
, count
, tdiff
;
716 krb5_error_code retval
;
717 struct timeval timestamp
;
718 kdb_log_context
*log_ctx
;
719 kdb_hlog_t
*ulog
= NULL
;
720 uint32_t ulogentries
;
723 ulogentries
= log_ctx
->ulogentries
;
726 * Check to make sure we don't have a corrupt ulog first.
728 if (ulog
->kdb_state
== KDB_CORRUPT
) {
729 ulog_handle
->ret
= UPDATE_ERROR
;
730 return (KRB5_LOG_CORRUPT
);
733 gettimeofday(×tamp
, NULL
);
735 tdiff
= timestamp
.tv_sec
- ulog
->kdb_last_time
.seconds
;
736 if (tdiff
<= ULOG_IDLE_TIME
) {
737 ulog_handle
->ret
= UPDATE_BUSY
;
742 * We need to lock out other processes here, such as kadmin.local,
743 * since we are looking at the last_sno and looking up updates. So
744 * we can share with other readers.
746 retval
= krb5_db_lock(context
, KRB5_LOCKMODE_SHARED
);
751 * We may have overflowed the update log or we shrunk the log, or
752 * the client's ulog has just been created.
754 if ((last
.last_sno
> ulog
->kdb_last_sno
) ||
755 (last
.last_sno
< ulog
->kdb_first_sno
) ||
756 (last
.last_sno
== 0)) {
757 ulog_handle
->lastentry
.last_sno
= ulog
->kdb_last_sno
;
758 (void) krb5_db_unlock(context
);
759 ulog_handle
->ret
= UPDATE_FULL_RESYNC_NEEDED
;
761 } else if (last
.last_sno
<= ulog
->kdb_last_sno
) {
764 indx
= (sno
- 1) % ulogentries
;
766 indx_log
= (kdb_ent_header_t
*)INDEX(ulog
, indx
);
769 * Validate the time stamp just to make sure it was the same sno
771 if ((indx_log
->kdb_time
.seconds
== last
.last_time
.seconds
) &&
772 (indx_log
->kdb_time
.useconds
== last
.last_time
.useconds
)) {
775 * If we have the same sno we return success
777 if (last
.last_sno
== ulog
->kdb_last_sno
) {
778 (void) krb5_db_unlock(context
);
779 ulog_handle
->ret
= UPDATE_NIL
;
783 count
= ulog
->kdb_last_sno
- sno
;
785 ulog_handle
->updates
.kdb_ulog_t_val
=
786 (kdb_incr_update_t
*)malloc(
787 sizeof (kdb_incr_update_t
) * count
);
789 upd
= ulog_handle
->updates
.kdb_ulog_t_val
;
792 (void) krb5_db_unlock(context
);
793 ulog_handle
->ret
= UPDATE_ERROR
;
797 while (sno
< ulog
->kdb_last_sno
) {
798 indx
= sno
% ulogentries
;
800 indx_log
= (kdb_ent_header_t
*)
803 (void) memset(upd
, 0,
804 sizeof (kdb_incr_update_t
));
806 (char *)indx_log
->entry_data
,
807 indx_log
->kdb_entry_size
, XDR_DECODE
);
808 if (!xdr_kdb_incr_update_t(&xdrs
, upd
)) {
809 (void) krb5_db_unlock(context
);
810 ulog_handle
->ret
= UPDATE_ERROR
;
811 return (KRB5_LOG_CONV
);
814 * Mark commitment since we didn't
815 * want to decode and encode the
816 * incr update record the first time.
818 upd
->kdb_commit
= indx_log
->kdb_commit
;
824 ulog_handle
->updates
.kdb_ulog_t_len
= count
;
826 ulog_handle
->lastentry
.last_sno
= ulog
->kdb_last_sno
;
827 ulog_handle
->lastentry
.last_time
.seconds
=
828 ulog
->kdb_last_time
.seconds
;
829 ulog_handle
->lastentry
.last_time
.useconds
=
830 ulog
->kdb_last_time
.useconds
;
831 ulog_handle
->ret
= UPDATE_OK
;
833 (void) krb5_db_unlock(context
);
838 * We have time stamp mismatch or we no longer have
839 * the slave's last sno, so we brute force it
841 (void) krb5_db_unlock(context
);
842 ulog_handle
->ret
= UPDATE_FULL_RESYNC_NEEDED
;
849 * Should never get here, return error
851 ulog_handle
->ret
= UPDATE_ERROR
;
852 return (KRB5_LOG_ERROR
);
856 ulog_set_role(krb5_context ctx
, iprop_role role
)
858 kdb_log_context
*log_ctx
;
860 if (!ctx
->kdblog_context
) {
861 if (!(log_ctx
= malloc(sizeof (kdb_log_context
))))
863 ctx
->kdblog_context
= (void *)log_ctx
;
865 log_ctx
= ctx
->kdblog_context
;
867 log_ctx
->iproprole
= role
;