OpenSolaris_b135/lib/libnisdb/yptol/lock_update.c

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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
 * or http://www.opensolaris.org/os/licensing.
 * 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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * DESCRIPTION: Contains code supporting the 'update in progress' flag. This is
 *		a near copy of lock flag code (in
 *		usr/src/cmd/ypcmd/shared/lockmp.c) If we implement a clean
 *		version	of the locking code this file will probably disappear.
 *
 *		These locks are held while a map is being updated from the
 *		DIT. They prevent a second update being started while this is
 *		in progress. This is independant from the `lockmap` mechanism
 *		which protects maps, generally for a much shorter period,
 *		while their control structures are modified.
 */

#include <unistd.h>
#include <syslog.h>
#include <sys/mman.h>
#include <thread.h>
#include <synch.h>
#include <ndbm.h>
#include <strings.h>
#include "ypsym.h"
#include "shim.h"
#include "yptol.h"
#include "../ldap_util.h"

#define	LOCKFILE "/var/run/yp_mapupdate"
struct updatearray {
	mutex_t		updatenode[MAXHASH];
};
typedef struct updatearray updatearray;

/*
 * Cross-process robust mutex locks.
 * Provide synchronization between YP processes
 * by implementing an exclusive locking mechanism
 * via a memory-mapped file.
 */
static struct updatearray	*shmupdatearray;
static int	lockfile;

bool_t
init_update_locks_mem()
{
	int iiter, rc;
	int ebusy_cnt = 0;

	/*
	 * Initialize cross-process locks in memory-mapped file.
	 */
	for (iiter = 0; iiter < MAXHASH; iiter++) {
		if (rc = mutex_init(&(shmupdatearray->updatenode[iiter]),
		    USYNC_PROCESS | LOCK_ROBUST, 0)) {
			if (rc == EBUSY) {
				ebusy_cnt++;
			} else {
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"init_update_locks_mem():mutex_init():"
					"error=%d", rc);
				return (FALSE);
			}
		}
	}

	/*
	 * EBUSY for all locks OK, it means another process
	 * has already initialized locks.
	 */
	if ((ebusy_cnt > 0) && (ebusy_cnt != MAXHASH)) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
		"%s inconsistent. Remove this file and restart NIS (YP)",
								LOCKFILE);
		return (FALSE);
	}
	return (TRUE);
}

bool_t
init_update_lock_map()
{
	char buff[ sizeof (updatearray) ];
	int write_cnt, lf_size;
	struct stat fdata;

	/*
	 * Locking file initialization algorithm, with recovery mechanism.
	 * This mechanism has been devised to ensure proper creation
	 * of a memory-mapped lock file containing mutexes for robust,
	 * inter-process communication.
	 * File name is /var/run/yp_mapupate (LOCKFILE).  It might or might
	 * not exist.
	 *
	 * Algorithm:
	 * Try to open the file. If file doesn't exist, or size is too small,
	 * create/rewrite the file, m-map it into memory and initialize the
	 * mutexes in it.
	 * If file exists and size is at least large enough, assume it's a
	 * good file, and m-map the lock structure directly to it.
	 *
	 * Recovery from inconsistent state is easy - simply delete the file
	 * and restart NIS (YP).
	 */

	lockfile = open(LOCKFILE, O_RDWR|O_CREAT, 0600);
	if (lockfile != -1) {
		if (lockf(lockfile, F_LOCK, 0) == 0) {
			if (fstat(lockfile, &fdata) == 0) {
				lf_size = fdata.st_size;
				if (lf_size < sizeof (updatearray)) {
					bzero(buff, sizeof (buff));
					if ((write_cnt = write(lockfile, buff,
					    sizeof (buff)) != sizeof (buff))) {
						if (write_cnt < 0) {
							logmsg(MSG_NOTIMECHECK,
								LOG_ERR,
						"write(%s) => errno=%d",
							    LOCKFILE, errno);
						} else {
							logmsg(MSG_NOTIMECHECK,
								LOG_ERR,
		    "write(%s) => %d!=%d: wrong number of bytes written",
							    LOCKFILE,
							    write_cnt,
							    sizeof (buff));
						}
						lockf(lockfile, F_ULOCK, 0);
						close(lockfile);
						return (FALSE);
					}
				}
			} else {
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
				    "fstat(%s) => errno=%d", LOCKFILE, errno);
				lockf(lockfile, F_ULOCK, 0);
				close(lockfile);
				return (FALSE);
			}
		} else {
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
			    "lockf(%s,F_LOCK) => errno=%d", LOCKFILE, errno);
			close(lockfile);
			return (FALSE);
		}
	} else {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
			"open(%s) => errno=%d", LOCKFILE, errno);
		return (FALSE);
	}

	/*
	 * File exists with correct size, is open, and we're holding
	 * the file lock.
	 */
	shmupdatearray = (updatearray *)mmap((caddr_t)0, sizeof (updatearray),
	    PROT_READ | PROT_WRITE, MAP_SHARED, lockfile, 0);
	if (shmupdatearray == MAP_FAILED) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"mmap(%s) => errno=%d", LOCKFILE, errno);
		lockf(lockfile, F_ULOCK, 0);
		close(lockfile);
		return (FALSE);
	}

	/*
	 * If we wrote zeroes to the file, we also need to initialize
	 * the mutex locks.
	 */
	if (lf_size < sizeof (updatearray)) {
		if (init_update_locks_mem() == FALSE) {
			lockf(lockfile, F_ULOCK, 0);
			close(lockfile);
			if (remove(LOCKFILE) != 0) {
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"remove(%s) => errno=%d: Please delete file",
							LOCKFILE, errno);
			}
			return (FALSE);
		}
	}

	if (lockf(lockfile, F_ULOCK, 0) != 0) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
			"lockf(%s,F_ULOCK) => errno=%d", LOCKFILE, errno);
		close(lockfile);
		return (FALSE);
	}

	if (close(lockfile) == 0) {
		return (TRUE);
	} else {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"close(%s) => errno=%d", LOCKFILE, errno);
		return (FALSE);
	}
}

suc_code
lock_map_update(map_ctrl *map)
{
	int hashval = map->hash_val;
	int rc;

	/*
	 * Robust, cross-process lock implementation
	 */
	rc = mutex_lock(&(shmupdatearray->updatenode[hashval]));
	while (rc != 0) {
		switch (rc) {
		case EOWNERDEAD:
			/*
			 * Previous lock owner died, resetting lock
			 * to recover from error.
			 */
			rc = mutex_consistent(
			    &(shmupdatearray->updatenode[hashval]));
			if (rc != 0) {
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"mutex_consistent(): error=%d", rc);
				return (FAILURE);
			}
			rc = mutex_unlock(
			    &(shmupdatearray->updatenode[hashval]));
			if (rc != 0) {
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"mutex_unlock(): error=%d", rc);
				return (FAILURE);
			}
			break;
		default:
			/*
			 * Unrecoverable problem - nothing to do
			 * but exit YP and delete lock file.
			 */
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
						"mutex_lock(): error=%d", rc);
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"Please restart NIS (ypstop/ypstart)");
			if (remove(LOCKFILE) != 0) {
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"remove(%s) => errno=%d: Please delete file",
							LOCKFILE, errno);
			}
			return (FAILURE);
		}
		rc = mutex_lock(&(shmupdatearray->updatenode[hashval]));
	}

	/* Success */
	return (SUCCESS);
}


suc_code
unlock_map_update(map_ctrl *map)
{
	int hashval = map->hash_val;
	int rc;

	rc = mutex_unlock(&(shmupdatearray->updatenode[hashval]));
	if (rc != 0) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
						"mutex_unlock(): error=%d", rc);
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"Please restart NIS (ypstop/ypstart)");
		if (remove(LOCKFILE) != 0) {
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
			    "remove(%s) => errno=%d: Please delete file",
			    LOCKFILE, errno);
		}
		return (FAILURE);
	}

	/* Success */
	return (SUCCESS);
}

/*
 * FUNCTION :   is_map_updating()
 *
 * DESCRIPTION: Determines if a map is currently locked for update
 *
 * GIVEN :      Pointer to map_ctrl structure
 *
 * RETURNS :    TRUE = Map is locked
 *              FALSE = Map is not locked
 */
bool_t
is_map_updating(map_ctrl *map)
{
	int ret;

	/* It appears not to be possible to just read a mutex. Try to lock it */
	ret = mutex_trylock(&(shmupdatearray->updatenode[map->hash_val]));

	if (0 != ret) {
		/* Didn't get the lock ... was already locked */
		return (TRUE);
	}

	/* Didn't need the lock so free it again */
	mutex_unlock(&(shmupdatearray->updatenode[map->hash_val]));
	return (FALSE);
}

/*
 * FUNCTION :	try_lock_map_update()
 *
 * DESCRIPTION: Tries to to lock a map for update.
 *
 * GIVEN :	Pointer to the map to lock
 *
 * RETURNS :	0 = The map is now locked
 *		EBUSY = The map was already locked lock not obtained.
 *		Other = There was an error
 */
int
try_lock_map_update(map_ctrl *map)
{
	int hashval = map->hash_val;
	int rc;

	/*
	 * Robust, cross-process lock implementation
	 *
	 * Keep trying until either lock is obtained or somebody else gets it.
	 */
	while (1) {
		rc = mutex_trylock(&(shmupdatearray->updatenode[hashval]));

		switch (rc) {

		case 0:
		case EBUSY:
			/* Either got it or somebody else has it */
			return (rc);

		case EOWNERDEAD:
			/*
			 * Previous lock owner died, resetting lock
			 * to recover from error.
			 */
			rc = mutex_consistent(
			    &(shmupdatearray->updatenode[hashval]));
			if (rc != 0) {
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"mutex_consistent(): error=%d", rc);
				return (rc);
			}
			rc = mutex_unlock(
			    &(shmupdatearray->updatenode[hashval]));
			if (rc != 0) {
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"mutex_unlock(): error=%d", rc);
				return (rc);
			}
			break;
		default:
			/*
			 * Unrecoverable problem - nothing to do
			 * but exit YP and delete lock file.
			 */
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
						"mutex_lock(): error=%d", rc);
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"Please restart NIS (ypstop/ypstart)");
			if (remove(LOCKFILE) != 0) {
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"remove(%s) => errno=%d: Please delete file",
							LOCKFILE, errno);
			}
			return (rc);
		}
	}
}