nis_log_svc.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 1990-2001 by Sun Microsystems, Inc.
* All rights reserved.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
*
* This is the other part of the NIS+ logging facility. It contains functions
* used by the service. The other part, nis_log_common.c contains functions
* that are shared between the service and nislog the executable.
*
* These functions are implemented on top of the mmap primitives and
* will change when user level threads are available. Mostly they can benefit
*
* The normal sequence of events is as follows :
* At boot time :
* map_log(SHARED);
*
* For each update :
* xid = begin_transaction(who);
* add_update(entry);
* ...
* Then on commit :
* end_transaction(xid);
* Else on abort :
* abort_transaction();
*/
#include <time.h>
#include <syslog.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include "nis_proc.h"
#include "log.h"
#include "nis_mt.h"
#include "nisdb_mt.h"
/*
* in_checkpoint, in_update, updatetime_cache moved to nis_log_common.c,
* extern'd here.
*/
extern NIS_HASH_TABLE *updatetime_cache;
extern int in_checkpoint;
extern int in_update;
extern int __nis_logfd;
/*
* variable used to assure that nisping warnings both do not flood the log
* and also do get sent out periodically (moved to nis_log_common.c).
*/
extern long next_warntime;
extern nis_object* get_root_object();
/*
* abort_transaction()
*
* This function backs out a transaction in progress that was interrupted
* by a server crash or some other unrecoverable error.
* Note that the updates are backed out in reverse order from the way they
* were applied. This is currently not strictly required for correctness
* but will be required if a gang-modify operation is ever supported.
*/
int
int n_xid;
{
int error;
int prevWasModify, doingModify = 0;
/*
* Check to see if the update was written to the
* log.
*/
"Unable to decode transaction in log! Data LOST");
continue;
}
/*
* Toggle the TSD 'doingModify' flag if necessary.
*/
if (doingModify && !prevWasModify) {
} else if (!doingModify && prevWasModify) {
__nisdb_get_tsd()->doingModify = 0;
}
case ADD_NAME :
if (stat != NIS_SUCCESS) {
abort();
}
abort();
}
/* Successfully backed it out */
break;
case REM_NAME :
if (stat != NIS_SUCCESS) {
abort();
}
"abort_transaction: Internal database error.");
abort();
}
/* Successfully backed it out */
break;
/* Skip the new modified object */
case MOD_NAME_NEW :
break;
/* Add the old modified object over the new one */
case MOD_NAME_OLD :
"abort_transaction: Internal database error.");
abort();
}
if (stat != NIS_SUCCESS) {
abort();
}
}
/* Successfully backed it out */
break;
case ADD_IBASE :
"abort_transaction: Internal error, log entry corrupt (%s).",
__make_name(&le));
abort();
}
if (stat != NIS_SUCCESS) {
abort();
}
"abort_transaction: Internal database error.");
abort();
}
/* Successfully backed it out */
break;
case REM_IBASE :
if (stat != NIS_SUCCESS) {
abort();
}
"abort_transaction: Internal error, log entry corrupt (%s).",
__make_name(&le));
abort();
}
} else {
"abort_transaction: Internal database error.");
abort();
}
break;
}
/* back up one update */
}
return (-1); /* Didn't back them all out! */
}
sync_header();
return (0);
}
static int
{
stamp_item *si;
if (si) {
return (0);
}
if (! si) {
abort();
}
abort();
}
/*
* If we can find it now, then __nis_insert_item_mt()
* probably failed because the item already existed, which
* is OK. If we can't find it, it's still possible that
* everything's fine (item didn't exist when we first tried
* to find it, had been created when we tried to insert, and
* then had been removed when we tried to find it again),
* so we just issue a warning.
*/
if (si == 0) {
"item for \"%s\"", name);
}
}
return (1); /* first time we've seen it. */
}
static void
{
if (si) {
return;
}
if (! si) {
abort();
}
abort();
}
0) {
/*
* If we can find it now, then __nis_insert_item_mt()
* probably failed because the item already existed, which
* is OK. If we can't find it, it's still possible that
* everything's fine (item didn't exist when we first tried
* to find it, had been created when we tried to insert, and
* then had been removed when we tried to find it again),
* so we just issue a warning.
*/
if (si == 0) {
"item for \"%s\"", name);
}
}
}
/*
* This routine inserts the specified update timestamp for the specified
* directory into the new checkpointed data area. It tries to insert a
* timestamp with the xid specified. If this xid is less than or equal
* to the last xid appended to the new checkpointed data area, then
* increment the offset_xid and use the (last_xid + offset_xid) as the
* new xid for the timestamp entry.
*/
static void
/* when xid to be inserted is <= the last */
/* xid in the new checkpoint area. */
/* timestamp should go. */
{
return;
/* value 20 below is a random added for extra padding */
abort();
}
(*offset_xid)++;
} else
"insert_chkpt_stamp(): xdr_log_entry failed.");
abort(); }
*addr_p += o_upd_size;
if (verbose) {
"checkpoint_log: tombstone xid %d, time %d (%s) created",
}
}
/*
* make_tombstone creates the timestamp (tombstone) for the name
* specified.
* If no old timestamp found in old_stamp_list, just stamp it with utime.
* If it's already stamped with an old timestamp don't stamp it again.
* If it's not already stamped with the old timestamp, stamp it with utime.
*
* make_tombstone will also remove the item from old_stamp_list.
*/
static void
{
if (verbose)
"make_tombstone: making timestamp %d for %s",
/* set up the log entry */
add_update(&le);
sync_header();
}
if (o_si) {
if (o_si != 0) {
}
}
}
/*
* checkpoint_log()
*
* This function removes all transactions up to the indicated time from
* the log file.
*
* It returns 0 on failure, 1 on success.
*/
int
{
int error;
stamp_item *si;
char backup[1024];
if (verbose)
return (0);
{
while ((e = __nis_pop_item_mt(&stamp_list)) != 0) {
XFREE(e);
}
}
"checkpoint_log: Unable to checkpoint, log unstable.");
return (0);
}
/* XXX This should be a spin locks for the transaction funcs */
if (fd == -1) {
"checkpoint_log: Unable to checkpoint, can't open backup log (%s).",
backup);
return (0);
}
/*
* Make a backup of the log in two steps, write the size of the log
* and then a copy of the log.
*/
sizeof (log_hdr);
"checkpoint_log: Unable to checkpoint, disk full.");
return (0);
}
if (verbose)
"checkpoint_log: Backup log %s created.", backup);
/*
* Now set the state to RESYNC so if we have to recover and read
* this back in, and we get screwed while reading it, we won't
* get into major trouble. This still leaves open one window :
* a) Start checkpoint
* b) Successfully backup to BACKUP
* c) Set log state to CHECKPOINT
* c) crash.
* d) Reboot and start reading in the backup log
* e) crash.
* f) reboot and now the log appears to be resync'ing without
* all of its data.
*/
"checkpoint_log: Unable to checkpoint, disk full.");
sync_header();
return (0);
}
if (verbose)
backup);
/* If we crash here we're ok since the log hasn't changed. */
sync_header();
/*
* If this transaction hasn't been fully replicated then
* leave it in the log. Othersize don't bother moving it.
*/
if (verbose)
"checkpoint_log: cannot read transaction log entry.");
/* free up any malloc'd storage */
return (0);
}
/* ignore dummy entries used by delta-update */
continue;
}
/* check the old stamp list */
o_si = (old_stamp_item *)
&old_stamp_list));
/* insert the old timestamp */
&addr_p);
num++;
/*
* if the the trasaction saved is of UPD_STAMP
* type, then add it into the old_stamp_list
* and mark it as stamped.
*/
o_si = (old_stamp_item *)
&old_stamp_list));
if (o_si != 0)
}
&old_stamp_list, -1);
if (verbose)
"checkpoint_log: Transaction %d, time %d (%s) kept",
num++;
} else {
if (verbose)
"checkpoint_log: Transaction %d, time %d (%s) removed",
}
/* free up any malloc'd storage */
}
if (num == 0) {
/* Deleted all of the entries. */
if (verbose)
"checkpoint_log: all entries removed.");
sync_header();
if (ret == -1) {
"checkpoint_log: cannot truncate transaction log file");
abort();
}
if (ret == -1) {
"checkpoint_log: cannot increase transaction log file size");
abort();
}
if (ret != 1) {
"cannot write one character to transaction log file");
abort();
}
__nis_logsize = sizeof (log_hdr);
perror("msync:");
abort();
}
} else {
if (verbose)
"checkpoint_log: some entries removed.");
sync_header();
if (error) {
"checkpoint_log: Checkpoint failed, unable to resync.");
abort();
}
}
/*
* Write back "last update" stamps for all of the directories
* we've processed.
*/
while (stamp_list.first) {
int srv;
"checkpoint_log: removed timestamp for %s (not a directory)",
/*
* If we're a replica, we want to keep
* the last_update() time as our
* UPD_STAMP. We may have LDAP-sourced
* updates that are more recent, but
* that shouldn't show up to a
* NIS_CPTIME.
*/
if (srv == 1) {
} else {
ttime =
if (ttime == 0)
}
ttime);
} else {
if (verbose)
"checkpoint_log: removed timestamp for %s (we no longer serve)",
}
} else {
/*
* If nis_lookup() failed, we most likely
* aren't the master. Hence, we retain the
* existing last_update() time as our
* UPD_STAMP
*/
if (ttime == 0)
}
} else {
if (verbose)
"checkpoint_log: removed timestamp for %s (no longer exists)",
}
}
sync_header();
next_warntime = 0l;
if (verbose)
/* re-generate the timestamp cache */
return (1);
}
/*
* last_update()
*
* This function scans the log backwards for the last timestamp of an update
* that occurred on the named directory. If it cannot find an update for that
* directory it returns 0L.
*/
{
/*
* Last update time for the root object is gotten from the object
* itself, or, it that does not exist, 0.
*/
if (root_object_p(name)) {
if (robj = get_root_object()) {
return (ttime);
} else {
return (0L);
}
}
/*
* Always uses the cached update timestamp. However, if somehow
* the cache is not available, it will fallback to the old way of
* using the transaction log. In a readonly child, we insist on
* using the cache to avoid using corrupted data in the shared
* transaction log address space.
*/
if (updatetime_cache != NULL) {
stamp_item *si;
if (si) {
if (verbose)
"last_update: (cached) returning %d for %s.",
/*
* If the update time is ULONG_MAX, the directory
* is invalid and should be dumped in its entirety.
* That's supposed to be handled during startup
* (the 'invalid_directory' code in nis_main.c),
* but if we got ULONG_MAX here, we do what we
* can to force a full dump by returning zero.
*/
}
} else {
if (verbose)
if (readonly) {
if (verbose)
"last_update: returning 0 for %s", name);
return (0L);
}
while (upd) {
if (verbose)
"last_update: (log) returning %d for %s.",
}
}
}
if (verbose)
return (0L);
}
/*
* entries_since()
*
* This function returns a malloc'd array of entries for the passed
* directory since the given timestamp. This is used when dumping
* deltas to the replicas. It returns NULL if it can't find any
* entries.
*
* As part of the fix for bug 4186012, avoid including entries with
* a time stamp greater than or equal to the current time. This avoids
* distributing information about entries in the current second until
* that time has passed. This means that, say, replicas see all the
* updates for that second rather than there being a question about
* which of the many possible updates with the same time stamp they
* last saw. In the event of there being outstanding entries the
* directory is added to the ping list so that the master will, in
* time, nisping the replicas again.
*/
void
{
int n_updates, total_updates, i;
char name[NIS_MAXPATH];
if (verbose)
(void) lockTransLog("entries_since", 0, 0);
while (first) {
break;
}
if (! first) {
name);
unlockTransLog("entries_since", 0);
return;
}
total_updates = 128;
if (! updates) {
if (verbose)
unlockTransLog("entries_since", 0);
return;
}
n_updates = 0;
if (verbose)
"entries_since: avoiding returning entries updated "
/*
* Make sure we ping the replicas
* soon. The master will only ping
* replicas once the updateBatchingTimeout
* has passed, so we estabtabllish a ping
* time that's the current time, minus the
* updateBatchingTimeout, plus one. This
* ensures the ping doesn't occur this second
* but ASAP afterwards.
*/
&ping_list);
break;
}
if (verbose)
total_updates += 128;
if (! updates) {
"entries_since(): out of memory");
unlockTransLog("entries_since", 0);
return;
}
for (i = 0; i < n_updates; i++)
}
}
}
if (n_updates == 0) {
unlockTransLog("entries_since", 0);
return;
}
unlockTransLog("entries_since", 0);
return;
}
for (i = 0; i < n_updates; i++) {
/*
* NOTE : Since we're xdr decoding we don't really care
* what the "size" value is, we're only going to decode
* one log entry anyway.
*/
break;
}
}
unlockTransLog("entries_since", 0);
}