nis_log_common.c revision 49e7ca4919cec3229f6fab9730bafc7cf24dab23
/*
* 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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
*
* This module contains logging functions that are common to the service and
* the log diagnostic utilities.
*/
#include <syslog.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include "nis_svc.h"
#include "nis_proc.h"
#include "log.h"
#include "nisdb_mt.h"
#include "ldap_util.h"
extern void __db_disallowLDAP();
extern void __db_allowLDAP();
/* string guard, it is always safe to print nilptr(char_pointer) */
#define nilptr(s) ((s) ? (s) : "(nil)")
static int pagesize = 0;
int in_checkpoint = FALSE;
int need_checkpoint = 0;
extern NIS_HASH_TABLE old_stamp_list;
static void add_updatetime();
/*
* 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).
*/
long next_warntime = 0l;
extern int verbose;
int __nis_logfd = -1;
/* data and routines for the updatetime cache begin here */
/*
* updatetime_cache is a cache copy of the update timestamp for all
* the directories served by this server. It is kept in sync with the
* log file by adding time stamps in end_transaction(), and rebuilding
* the cache when checkpoint_log has finished. Using the cache is
* vital in readonly children, as the log file may be inconsistant. It
* is an optimization only otherwise.
*/
void init_updatetime(void); /* forward */
/*
* Purges the update timestamp cache
* Frees everything, and sets updatetime_cache to NULL.
*/
static void
purge_updatetime(void)
{
if (updatetime_cache != NULL) {
/* clean up the timestamp cache */
stamp_item *si;
}
}
}
static void
{
stamp_item *si;
if (updatetime_cache == NULL) {
if (updatetime_cache == NULL)
return;
}
if (si) {
/* already cached - update the timestamp */
return;
}
if (! si) {
return;
}
return;
}
if (si != 0) {
/*
* Apparently, another thread just created this item.
* Update the utime only if it increases.
*/
updatetime_cache, -1);
} else {
"add_updatetime(): error inserting %ld for %s",
}
}
}
/*
* Routine to generate the cache copy of the update timestamp.
* This routine would be static, and we would rely on initializing the
* updatetime_cache when we tried to add_updatetime() to an empty cache,
* except that it can take some time to traverse the log, and the client
* could time out.
*
* We traverse the log from old to new, so that the newer updates will
* overwrite the older ones.
*
* Recursive calls would be possible if init_updatetime called
* add_updatetime, which ran out of memory, and freed the cache. Then
* the next call to add_updatetime from within init_updatetime would
* recursively call init_updatetime. If we run out of room building
* the cache, we just free everything and don't build it.
*/
void
init_updatetime(void)
{
if (updating_cache)
return; /* won't do recursive updates */
if (updatetime_cache != NULL) {
"init_updatetime(updatetime_cache)");
"init_updatetime(updatetime_cache)");
if (updatetime_cache != 0) {
/* OK, some other thread got in before us */
"init_updatetime(updatetime_cache)");
return;
}
}
if (! updatetime_cache) {
"init_updatetime(updatetime_cache)");
return;
}
while (upd) {
}
/*
* We need the code below only when this file is being
* compiled for rpc.nisd or nislog. The NEED_DIROBJ flag is
* declared in the CCFLAGS for rpc.nisd and nislog.
*/
#ifdef NEED_DIROBJ
/*
* In general, if we're a replica and are mapping from LDAP,
* the last update time for a directory isn't necessarily
* equal to the master's notion of same. Hence, if available,
* we prefer UPD_STAMP:s.
*
* However, if we're the master, the UPD_STAMP isn't necessarily
* up to date, so we still use the last update time.
*/
nis_object *obj = 0;
/*
* Check if we're the master. We do this by asking
* __directory_object_msg() for the object, with the
* 'is_master' flag set. If we don't get an object,
* we aren't the master.
*/
0);
continue;
}
}
#endif /* NEED_DIROBJ */
}
/* data and routines for the updatetime cache end here */
void
{
if (! pagesize) /* msync misfeature */
pagesize = getpagesize();
perror("msync:");
abort();
}
}
void
{
if (! pagesize) /* msync misfeature */
pagesize = getpagesize();
/* round start to the nearest page */
/* round end to the nearest page */
(~(pagesize-1));
perror("msync:");
abort();
}
}
/*
* call msync() on the entire transaction log -- used for fast delta
* updates.
*/
void sync_log() {
if (! pagesize) /* msync misfeature */
pagesize = getpagesize();
/* start at __nis_log */
/* round end to the nearest page */
perror("msync:");
abort();
}
}
/*
* so that we can lock the trans.log without incurring the overhead of
* begin_transaction until we really have to.
*
* If 'wr' is set, a write lock is requested, and in this case, the
* 'trylock' parameter is also honored (it's ignored for read locks).
*/
int
int lstat;
if (msg == 0)
msg = "lockTransLog";
if (wr) {
if (trylock)
else
} else {
}
if (lstat != 0) {
"%s: [%d] transaction log busy; try later",
msg, pthread_self());
else
"%s: transaction log lock error %d",
} else {
if (verbose)
"%s: [%d] holding transaction log %s lock",
}
return (lstat);
}
void
if (msg == 0)
msg = "unlockTransLog";
if (wr)
else
if (verbose)
"%s: [%d] released transaction log %s lock",
}
/*
* This function starts a logged transaction. When the function returns
* the log is in the "update" state. If the function returns 0 then
* the transaction couldn't be started (NIS_TRYAGAIN) is returned to
* the client. Otherwise it returns the XID that should be used in
* calls to add update.
*/
int
{
int lstat;
/* XXX for threads we set up a writer lock */
return (0);
/*
* Synchronization point. At this point we try to get
* exclusive access to the log. If this fails we return
* 0 so that the client can try again later.
*/
if (in_checkpoint) {
return (0);
}
return (0);
}
cur_princp = princp;
sync_header();
/* Returning with write lock held */
return (cur_xid);
}
/*
* end_transaction
*
* This function does the actual commit, by setting the log header
* to be stable, and to have the latest XID present. Further it updates
* the time of the last update in the transaction to be 'ttime'. This
* is the time that is sent to the replicates so that they will know
* when they've picked up all of the updates up to this point.
*
* When the update was sourced from LDAP, we don't want to change the
* cached update time; hence the 'addUpdateTime' parameter.
*/
int
/* Check to see if add_update was called at all */
abort();
}
}
sync_header();
/* update the updatetime cache */
if (addUpdateTime) {
}
return (0);
}
int
end_transaction(int xid) {
}
/*
* Freeze the log tail and the log counter during fast updates.
*/
void begin_update() {
sync_header();
}
/*
* Unfreeze the log tail and the log counter
*/
void end_update() {
last_upd_p = NULL;
sync_header();
}
/*
* add_update()
*
* This function adds an update from the current transaction into
* the transaction log. It gets a bit tricky because it "allocates"
* all of it's memory from the file. Basically the transactions are
* laid down as follows :
*
* : prev update :
* +----------------+
* | log_upd header |
* +----------------+
* | XDR Encoded |
* : object from :
* | log_entry |
* +----------------+
* | Directory name |
* +----------------+
* : next update :
*
* The macro XID_SIZE calculates the offset to the start of the next
* update.
* returns 0 if the msync fails, otherwise it returns the log time.
*
* add_update is now a wrapper for the same code, but without
* msync's. replica_update calls add_update_nosync directly (the log tail and
* the log counter are then frozen); all others calls are to add_update, when
* the tail and the counter are not frozen.
*/
sizeof (log_hdr);
sync_header();
return (res);
}
void **updp;
{
int ret;
if (in_checkpoint)
abort(); /* Can't happen, because begin_trans would fail */
sizeof (log_hdr);
/* need both lines to find update size */
/* see diagram above this function */
if (total_size >= __nis_filesize) {
if (ret == -1) {
"Cannot grow transaction log, error %s",
return (0);
}
if (ret != 1) {
"Cannot write one character to transaction log, error %s",
return (0);
}
}
if (last_upd_p)
else {
}
"NIS+ server needs to be checkpointed. Use \"nisping -C domainname\"");
}
}
/*
* If this operation is a table entry addition or removal, and the
* TSD 'doingModify' flag is set, use a special magic number.
*/
__nisdb_get_tsd()->doingModify) {
} else {
}
return (0);
}
if (updp)
if (last_upd_p)
else
last_upd_p = upd;
upd_cnt++;
need_checkpoint = 1;
return (1);
}
/*
* make_stamp() - moved from nis_subr_proc.c to this file since
* nisbackup also needs this.
*
* This function adds a "null" entry into the log that indicates either
* the directory is stable or gone. When a directory is deleted, a tombstone
* (entry with a timestamp of 0) is written to the log. This prevents prior
* activity on the log from being confused with current activity. When a
* directory is resynchronized with a full dump, timestamp information is
* lost so this is used to mark the directory as being stable up to that point.
*/
void
{
if (! add_update(&le))
} else {
"make_stamp: zero xid from begin_transaction\n");
}
}
/*
* nis_cptime()
*
* This function will ask the indicated replicate for the last
* update it has seen to the given directory.
*/
{
}
/*
* nis_cptime_msg()
*
* The guts of nis_cptime(). If "domsg" is FALSE, messages are suppressed,
* unless verbose mode is enabled.
*/
{
/* If we can't contact it, return the safe answer */
if (! clnt) {
if (verbose)
return (0);
}
"nis_cptime: RPC error srv='%s', dir='%s', err='%s'",
res = 0;
}
}
if (verbose)
return (res);
}
/*
* __make_name()
*
* This function prints out a nice name for a search entry.
*/
char *
{
static char namestr[2048];
int i;
else
namestr[0] = '\0';
else
}
}
return (namestr);
}
/*
* __log_resync()
*
* This function will relocate the log to its current position in
* memory. Errors returned :
* 0 success
* -1 Illegal Update
* -2 Missing Data
* -3 Not enough updates
*
* Private flag (p):
* FNISD called from nisd
* FNISLOG called from nislog
* FCHKPT called from checkpoint_log
*/
int
__log_resync(log, p)
int p;
{
int i, ret;
/*
* Resync the hard way. In this section of code we
* reconstruct the log pointers by calculating where
* the pieces would be placed. Our calcuation is
* verified by the presence of the appropriate MAGIC
* number at the address we've calculated. If any
* magic number isn't present, we note that the log
* is corrupt and exit the service. The user will
* have to either patch the log, or resync the slaves
* by hand. (Forced resync)
*
*/
if (verbose)
/*
* if there are any transactions, this is the first one.
*/
if (verbose) {
}
"__log_resync: Transaction #%d bad magic number", i);
if (p != FNISLOG) {
"__log_resync: log truncated, resync to propagate possibly lost changes");
} else {
"__log_resync: Transaction #%d has a bad magic number\n", i);
}
break; /* major corruption */
/*
* Verify that lu_size seems reasonable by checking that the address to be used
* for cur->lu_dirname falls inside the mapped section from the transaction log.
* Note: Test for "<=", since cur+sizeof(log_upd)+cur->lu_size could overflow a
* u_long.
*/
"__log_resync: Transaction #%d: bad size %d",
if (p != FNISLOG) {
"__log_resync: log truncated, resync to propagate possibly lost changes");
} else {
}
break; /* also major corruption */
}
/* Fix up the link lists */
if (prev)
sizeof (log_upd) +
/* move to next update in the list */
if (verbose) {
}
}
if (verbose)
if (prev) {
"__log_resync: Incomplete last update transaction, removing.");
i--;
break;
}
} else
} else {
}
}
if (verbose)
if (p == FNISLOG) {
/* called from nislog, don't ftruncate() or msync() */
return (0);
} else if (p == FCHKPT) {
/* called from checkpoint, truncate transaction log file */
if (ret == -1) {
"__log_resync: Cannot truncate transaction log file");
return (-1);
}
if (ret == -1) {
"__log_resync: cannot seek to begining of transaction log file");
return (-1);
}
if (ret == -1) {
"__log_resync: cannot increase transaction log file size");
return (-1);
}
if (ret != 1) {
"__log_resync: cannot write one byte to transaction log file");
return (-1);
}
}
/* Could take a while */
if (ret == -1) {
abort();
}
return (0);
}
/*
* map_log()
*
* This function maps in the logfile into the address space of thenis
* server process. After the log is successfully mapped it is "relocated"
* so that the pointers in the file are valid for the log's position in
* memory. Once relocated, the log is ready for use. This function returns
* 0 if the log file is OK and !0 if it is corrupted.
*
* Error numbers :
* -4 File not found.
* -5 Cannot MMAP file
* -6 Corrupt file, bad magic number in header.
* -7 Unknonwn (illegal) state value
*/
int
char *logname;
int p; /* Private flag: */
/* FNISD=called from nisd */
/* FNISLOG=from nislog */
{
long log_size;
return (-9);
if (p == FNISLOG) { /* called from nislog */
return (-4);
}
if (__nis_logfd == -1) {
return (-4);
}
/* Make this file as it doesn't exist */
if (ret == -1) {
"map_log: cannot seek to begining of transaction log file");
return (-1);
}
if (ret == -1) {
"map_log: cannot increase transaction log file size");
return (-1);
}
/* writing a / all the time seemed silly */
if (ret != 1) {
"map_log: cannot write one byte to transaction log file");
return (-1);
}
} else { /* transaction log file exists */
if (__nis_logfd == -1) {
return (-4);
}
}
if (p == FNISLOG) {
/* called from nislog */
} else {
/* called from nisd */
}
if ((int)(__nis_log) == -1) {
"Unable to map logfile of length %ld into address space.",
return (-5);
}
/* If it's NULL then we just created the file */
if (p == FNISD)
MS_SYNC);
} else {
return (-6);
}
}
case LOG_STABLE :
if (verbose)
if (error) {
return (error); /* resync failed */
}
break;
case LOG_RESYNC :
if (verbose)
return (error);
}
break;
case LOG_UPDATE :
if (verbose)
return (error);
}
/*
* If the last update was a dir invalidator,
* a full resync will happen and restore
* consistancy.
*/
}
/*
* Check to see if the update was written to the
* log.
*/
/* never made it, so we're done. */
return (0);
}
return (0);
}
/* Back out the change */
/*
* We're just trying to clean the local DB, and hence
* don't want to use the LDAP repository.
*/
ret = abort_transaction(0);
/*
* Although begin_transaction() returns with the
* translog lock held, we're executing in a different
* process. Since abort_transaction() will unlock,
* we must not.
*/
return (ret);
break;
case LOG_CHECKPOINT :
if (verbose)
if (p == FNISLOG) {
return (error);
}
if (fd == -1) {
return (-1);
}
if (error == -1) {
"map_log: unable to read backup");
return (-1);
}
"map_log: Backup log truncated, fatal error.");
return (-1);
}
/* Manually resync the log */
if (! error) {
MS_SYNC);
break;
}
"map_log: Unable to resync after checkpoint restore.");
return (error);
break;
default :
"Illegal log state, aborting.");
return (-7);
}
/* At this point everything should be cool. */
return (0);
}
/*
* This code is very similar to nis_name_of() in libnsl.
* When a server lives in its own domain, its database files are named
* relative to the parent domain (that is, where it's credentials are
* stored. This version of nis_name_of() will take that into account.
*
* This is probably not the best file to put this routine into, but
* it is shared by rpc.nisd, nisbackup, and nisrestore. All of these
* programs need a common routine to determine the name of a database file.
*/
/*
* relative_name()
* This internal function will remove from the NIS name, the domain
* name of the current server, this will leave the unique part in
* the name this becomes the "internal" version of the name. If this
* function returns NULL then the name we were given to resolve is
* bad somehow.
*
* A dynamically-allocated string is returned.
*/
char *s; /* string with the name in it. */
{
char *d;
char *buf;
name_pos p;
if (s == NULL)
return (NULL);
d = __nis_rpc_domain();
if (d == NULL)
return (NULL);
return (NULL);
return (buf);
}
p = nis_dir_cmp(buf, d);
/* 's' is above 'd' in the tree */
return (NULL);
}
/* Insert a NUL where the domain name starts in the string */
/* Don't return a zero length name */
if (buf[0] == '\0') {
return (NULL);
}
return (buf);
}