OpenSolaris_b135/lib/libnisdb/yptol/update.c

/*
 * 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
 * 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 2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

/*
 * DESCRIPTION: Contains the map update thread and related code.
 */

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

/* Enable standard YP code features defined in ypdefs.h */
USE_YP_PREFIX
USE_YP_MASTER_NAME
USE_YP_LAST_MODIFIED
USE_YP_INPUT_FILE
USE_YP_OUTPUT_NAME
USE_YP_DOMAIN_NAME
USE_YP_SECURE
USE_YP_INTERDOMAIN

/*
 * Decs
 */
suc_code update_from_dit(map_ctrl *, datum *);
void * update_thread(void *);

/*
 * Globals
 */
extern pid_t parent_pid;

/*
 * FUNCTION:	update_entry_if_required()
 *
 * DESCRIPTION:	Determines if an entry is to be updated and if it is does the
 *		update.
 *
 * GIVEN :	Pointer to the open map ctrl
 *		Pointer to the entry key
 *
 * RETURNS :	SUCCESS = Entry is in a state to be returned to the client
 *		i.e. either got updated, did not need to be updated or we are
 *		in a mode where it is acceptable to return out of date
 *		information.
 *		FAILURE = Entry need an update but it could not be done.
 */
suc_code
update_entry_if_required(map_ctrl *map, datum *key)
{

	/* Only update individual entries if entire map is */
	/* not being updated */
	if (is_map_updating(map))
		return (SUCCESS);

	/*
	 * If we are being asked for the order then need to check if
	 * the map is in need of an update. If it is then fake a
	 * recent order. The client will then read the map, using
	 * dbm_firstkey and this will do the update.
	 */
	if (0 == strncmp(key->dptr, yp_last_modified, yp_last_modified_sz)) {
		if (has_map_expired(map))
			update_timestamp(map->entries);
		return (SUCCESS);
	}

	/* Never update special keys. Have no TTLs */
	if (is_special_key(key))
		return (SUCCESS);

	if (!has_entry_expired(map, key))
		/* Didn't need an update */
		return (SUCCESS);

	/* Do the update */
	return (update_from_dit(map, key));
}

/*
 * FUNCTION:	update_from_dit()
 *
 * DESCRIPTION:	Called to update an entry from the DIT
 *
 * INPUTS:	Map control structure for an open map
 *		Entry key
 *
 * OUTPUTS:	SUCCESS = Update complete or we are in a mode where it is
 *		acceptable to return out of date information.
 *		FAILURE =  Update failed
 *
 */
suc_code
update_from_dit(map_ctrl *map, datum *key)
{
	datum dat;
	int ret;
	suc_code res;

	/*
	 * Netgroup maps are a special case we cannot update just one entry so
	 * update the entire map instead.
	 */
	if ((0 == strcmp(map->map_name, NETGROUP_BYHOST)) ||
		(0 == strcmp(map->map_name, NETGROUP_BYUSER))) {
			return (update_map_if_required(map, FALSE));
	}

	/* Read entry from the DIT */
	ret = read_from_dit(map->map_name, map->domain, key, &dat);

	/* Check that we got something */
	if (NULL == dat.dptr) {
		if (0 == ret) {
			/*
			 * In a mode where it is acceptable to return out of
			 * date information.
			 */
			logmsg(MSG_NOTIMECHECK, LOG_INFO,
				"LDAP inaccessible returning old information");
			return (SUCCESS);
		} else {
			/*
			 * In a mode where it is not acceptable to return out
			 * of date information.
			 *
			 * If the error positviely indicates that there is no
			 * such entry delete it. For errors where object may
			 * still exist in the DIT leave it.
			 */
			if (MAP_NO_MATCHING_KEY == ret) {
				/*
				 * Don't log errors. If the entry was not
				 * already present then no problem. The user
				 * just asked us for a non existant entry.
				 */
				dbm_delete(map->entries, *key);
				dbm_delete(map->ttl, *key);
			}
			return (FAILURE);
		}
	}

	/* Write it to DBM */
	res = dbm_store(map->entries, *key, dat, DBM_REPLACE);
	sfree(dat.dptr);

	if (SUCCESS != res)
		return (FAILURE);

	/* Update TTL */
	update_entry_ttl(map, key, TTL_RUNNING);

	return (SUCCESS);
}

/*
 * FUNCTION:	update_map_if_required()
 *
 * DESCRIPTION:	Called to update an entire map if it is out of date. Map ctrl
 *		must be locked before this is called. This handles checking if
 *		the map is already being updated. It is important that this is
 *		done atomically	with obtaining the maps update lock.
 *
 * INPUTS:	Map control structure for an open map
 *		Flag indication if we should wait for completion
 *
 * OUTPUTS:	SUCCESS = Map update initiated
 *		FAILURE =  Map update not initiated
 */
suc_code
update_map_if_required(map_ctrl *map, bool_t wait)
{
	thread_t tid;
	map_ctrl *new_map;
	suc_code res;
	long	 flags;

	if (wait) {
		/*
		 * Actually get the lock
		 *
		 * May block so unlock map_ctrl while it is done
		 */
		unlock_map_ctrl(map);
		res = lock_map_update(map);
		lock_map_ctrl(map);
		if (SUCCESS != res) {
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"Could not lock map %s for update",
								map->map_name);
			return (FAILURE);
		}
	} else {
		/* If not waiting try to get the lock */
		switch (try_lock_map_update(map)) {
			case 0:
				/*
				 * We got the lock. Continue to start an update.
				 */
				break;

			case EBUSY:
				/*
				 * Some one else got the lock. OK they are
				 * doing the update so we can just return.
				 */
				return (SUCCESS);

			default:
				/*
				 * Some serious problem with lock.
				 */
				return (FAILURE);
		}
	}

	/*
	 * If we get here are holding the update lock. Make a final check that
	 * nobody beat us to the map update while we were getting it.
	 */
	if (!has_map_expired(map)) {
		/* A big waste of time. Somebody else did the update */
		unlock_map_update(map);
		return (SUCCESS);
	}

	/*
	 * We got the lock and nobody beat us to doing the update. Start our
	 * own update.
	 *
	 * Thread will free the update lock when update is complete.
	 */


	/*
	 * Make a copy of the map_ctrl structure so the update thread has an
	 * independent version to work with. Note: Must not be on stack.
	 *
	 * On exit the update thread must free this.
	 */
	new_map = dup_map_ctrl(map);
	if (NULL == new_map) {
		unlock_map_update(map);
		return (FAILURE);
	}

	/*
	 * While thread is running unlock map so other processes can
	 * execute non update related accesses
	 */
	unlock_map_ctrl(map);

	flags = THR_BOUND | THR_NEW_LWP;

	/*
	 * If we are not going to thr_join then need to create detached.
	 * This prevents a zombie being left when nobody joins us.
	 */
	if (!wait && (getpid() == parent_pid))
		flags |= THR_DETACHED;

	/* Kick off update thread */
	if (0 != thr_create(NULL, NULL, update_thread, new_map,
							flags, &tid)) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"Could not create NIS update thread");
		free_map_ctrl(new_map);
		unlock_map_update(map);
		if (SUCCESS != lock_map_ctrl(map))
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
			"Could not acquire update lock for %s", map->map_name);
		return (FAILURE);
	}

	if (wait) {
		/* May block but no problem map_ctrl is already unlocked. */
		thr_join(tid, NULL, NULL);
	}

	/* Re acquire lock */
	if (1 != lock_map_ctrl(map)) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
			"Could not re-acquire lock for %s", map->map_name);
		return (FAILURE);
	}

	return (SUCCESS);
}

/*
 * FUNCTION:	update_thread()
 *
 * DESCRIPTION:	The update thread this is called to update an entire NIS map.
 *		if several NIS maps are found to be out of date several
 *		instances of this may be running at the same time.
 *
 *		Since we are using a duplicate map_ctrl we do not have to lock
 *		it. If we did would end up using the same mutex as the parent
 *		map ctrl an possibly deadlocking.
 *
 * INPUTS:	Map handle (because we need access to name and lock)
 *
 * OUTPUTS:	None exits when finished.
 */

void *
update_thread(void *arg)
{
	void *ret = (void *)-1;
	map_ctrl *map;

	/* Cast argument pointer to correct type */
	map = (map_ctrl *)arg;

	/* Actually do the work */
	if (SUCCESS == update_map_from_dit(map, FALSE))
		ret = 0;

	/* Update complete or failed */
	unlock_map_update(map);

	/* Free up duplicate copy of the map_ctrl */
	free_map_ctrl(map);

	thr_exit(ret);
}

/*
 * FUNCTION :	is_special_key()
 *
 * DESCRIPTION:	Works out if a given key is one of the special ones. We just
 *		check for the "YP_" prefix. This is not 100% safe but if
 *		valid keys with a "YP_" prefix exist in the DIT then a lot of
 *		other parts of NIS wont work.
 */
bool_t
is_special_key(datum *key)
{
	if (0 == strncmp(key->dptr, yp_prefix, yp_prefix_sz))
		return (TRUE);

	return (FALSE);
}