nis_callback.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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
*
* This module contains the functions that implement the callback
* facility. They are RPC library dependent.
*
* These callback functions set up and run the callback
* facility of NIS+. The idea is simple, a psuedo service is created
* by the client and registered with the portmapper. The program number
* for that service is included in the request as is the principal
* name of the _host_ where the request is being made. The server
* then does rpc calls to that service to return results.
*/
#include "mt.h"
#include <syslog.h>
#include <string.h>
#include <errno.h>
#include <malloc.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <tiuser.h>
#include <netdir.h>
#include <sys/netconfig.h>
#include <rpc/pmap_prot.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/nis_callback.h>
#include "nis_clnt.h"
#include "nis_local.h"
#include <thread.h>
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
#define CB_MAXENDPOINTS 16
/*
* Multi-threaded and Reentrant callback support:
*
* Eventually we would like to support simultaneous callbacks from
* multiple threads and callbacks within call backs. At the moment we
* does not support either multi-threaded or reentrant calls from rpc
* servers. Also note that the use of thread global data in this file
* precludes reentrant callbacks. Since these things cannot easily be
* made to work, we use a mutex lock to ensure that only one use of
* callbacks is taking place at a time. The lock must be held around
* all calls to __nis_core_lookup and nis_dump_r which do callbacks,
* as those functions are the ones which call __nis_init_callback and
* __nis_run_callback. The lock is defined here.
*/
/*
* __cbdata is the internal state which the callback routines maintain
* for clients. It is stored as a structure so that multithreaded clients
* may eventually keep a static copy of this on their Thread local storage.
*/
struct callback_data {
char pkey_data[1024];
int results;
void *cbuser;
int (*cback)();
void (*dispatch)();
};
/*
* Static function prototypes.
*/
static void
static void
static bool_t
static bool_t
static bool_t
static char *__get_clnt_uaddr(CLIENT *);
static void destroy_cbdata(void *);
static pthread_key_t cbdata_key;
static struct callback_data __cbdata_main;
/*
* In the MT rpc.nisd, the callback service is handled by one of the
* RPC auto MT service threads. Since nis_dump() assumes that one and
* the same thread both sets up the __cbdata and runs the callback
* service, we must use a sleight-of-hand to maintain that illusion.
* Hence the '__cbdata_dump', which points to the __cbdata of the
* thread running nis_dump().
*
* Protected by '__nis_callback_lock'.
*/
/*
* To synchronize the nis_dump() thread with its callback thread, we use
* a mutex, a condition variable, and a count of the number of times the
* dispatch function was invoked. We use the 'complete' field of the
* (struct callback_data) to signal completion, so for the nis_dump()
* case, 'complete' is protected by the '__nis_dump_mutex'.
*/
static int __nis_dump_cb_count = -1;
static struct timeval __nis_dump_lastcb = {0, 0};
/*
* Callback functions. These functions set up and run the callback
* facility of NIS. The idea is simple, a psuedo service is created
* by the client and registered with the portmapper. The program number
* for that service is included in the request as is the principal
* name of the _host_ where the request is being made. The server
* then does rpc calls to that service to return results.
*/
static void destroy_cbdata(void * cbdata)
{
if (cbdata)
}
static struct callback_data *my_cbdata(void)
{
struct callback_data *__cbdata;
if (thr_main())
return (&__cbdata_main);
else
return (__cbdata);
}
static char *
{
char *uaddr;
if (! nc)
return (NULL);
return (uaddr);
}
int
__nis_destroy_callback(void)
{
struct callback_data *__cbdata;
if (!__cbdata)
return (0);
}
}
/*
* Can only safely free __cbdata if:
*
* (a) It's not a pointer to the static __cbdata_main, and
*
* (b) We can set the TSD pointer to NULL first.
*/
if (__cbdata != &__cbdata_main &&
}
return (1);
}
/*
* __nis_init_callback()
* This function will initialize an RPC service handle for the
* NIS client if one doesn't already exist. This handle uses a
* "COTS" connection in a TIRPC.
* The server will either fork, or generate a thread to send us
* data and if the connection breaks we want to know (that means
* the server died and we have to return an error). It returns
* an endpoint if successful and NULL if it fails.
*
* NOTE : since we send the server the complete endpoint, including
* universal address, transport, and family, it doesn't need to contact
* our portmapper to find out what port we are using. Consequently we
* don't bother registering with the portmapper, this saves us from having
* to determine a unique program number.
*/
struct callback_data *
int (*cbfunc)(), /* Callback function */
void *userdata, /* Userdata, stuffed away for later */
void (*dispatch)()) /* Dispatch function for callback */
{
int nep; /* number of endpoints */
struct callback_data *__cbdata;
struct nd_mergearg ma;
void *nch;
return (NULL);
if (thr_main())
else
/* Check to see if we already have a service handle */
return (__cbdata);
}
/* Nope, then let's create one... */
}
if (! __cbdata) {
return (NULL);
}
/* callbacks are not authenticated, so do minimal srv description. */
/* Create the service handle(s) */
/*
* This gets a bit tricky. Because we don't know which transport
* the service will choose to call us back on, we have something
* of a delimma in picking the correct one here. Because of this
* we pick all of the likely ones and pass them on to the remote
* server and let it figure it out.
* XXX Use the same one as we have a client handle for XXX
*/
nch = (void *)setnetconfig();
nep = 0;
(nep == 0)) {
/* Step 0. XXX see if it is the same netid */
continue;
/* Step 1. Check to see if it is a virtual circuit transport. */
continue;
/* Step 2. Try to create a service transport handle. */
128, 8192);
"__nis_init_callback: Can't create SVCXPRT.");
continue;
}
/*
* When handling a callback, we don't want to impose
* any restrictions on request message size, and since
* we're not a general purpose server creating new
* connections, we don't need the non-blocking code
* either. Make sure those features are turned off
* for this connection.
*/
{
int connmaxrec = 0;
}
/*
* This merge code works because we know the netids match
* if we want to use a connectionless transport for the
* initial call and a connection oriented one for the
* callback this won't work. Argh.! XXX
*/
"__nis_init_callback: Can't get uaddr for %s transport.",
continue;
}
/* Step 3. Register it */
/* Step 4. Fill in the endpoint structure. */
nep++;
}
(void) endnetconfig(nch);
"__nis_init_callback: cannot create callback service.");
return (NULL);
}
return (__cbdata);
}
int (*cbfunc)(), /* Callback function */
void *userdata) /* Userdata, stuffed away for later */
{
struct callback_data *__cbdata;
else
return (NULL);
}
int (*cbfunc)(), /* Callback function */
void *userdata) /* Userdata, stuffed away for later */
{
struct callback_data *__cbdata;
(void) mutex_lock(&__nis_dump_mutex);
__nis_dump_cb_count = -1;
} else {
ret = 0;
}
__nis_dump_lastcb.tv_sec = 0;
__nis_dump_lastcb.tv_usec = 0;
(void) mutex_unlock(&__nis_dump_mutex);
return (ret);
}
/*
* Stub to handle requests...
* Note, as an optimization the server may return us more than one object.
* This stub will feed them to the callback function one at a time.
*/
static bool_t
struct callback_data *__cbdata,
int *results_ptr)
{
int i;
char buf[1024];
#ifdef lint
#endif /* lint */
*results_ptr = 0;
}
return (1); /* please do reply */
}
static bool_t
void *argp,
struct callback_data *__cbdata,
int *results_ptr) /* not used */
{
#ifdef lint
#endif /* lint */
return (0); /* don't attempt a reply */
}
static bool_t
struct callback_data *__cbdata;
int *results_ptr;
{
#ifdef lint
#endif /* lint */
return (1); /* non-zero => please do a reply */
}
/*
* __nis_run_callback()
*
* This is the other function exported by this module. The function
* duplicates the behaviour of svc_run() for regular rpc services,
* however it has the additional benefit that it monitors
* the state of the connection and if it goes away, it terminates
* the service and returns. Finally, when it returns, it returns
* the number of times the callback function was called for this
* session, or -1 if the session ended erroneously.
*/
int
{
struct callback_data *__cbdata;
int nfds = 0;
int pollret;
struct pollfd *svc_pollset = 0;
return (-1);
if (timeout)
else {
/* Default timeout when timeout is null */
}
if (nfds != svc_max_pollfd) {
sizeof (pollfd_t) * svc_max_pollfd);
}
if (nfds == 0) {
break; /* None waiting, hence return */
}
sizeof (pollfd_t) * svc_max_pollfd);
__rpc_timeval_to_msec(&tv))) {
case -1:
/*
* We exit on any error other than EBADF. For all
* other errors, we return a callback error.
*/
continue;
}
if (svc_pollset != 0)
return (- NIS_CBERROR);
case 0:
/*
* possible data race condition
*/
"__run_callback: data race condition detected and avoided.");
break;
}
/*
* Check to see if the thread servicing us is still
* alive
*/
xdr_netobj, (char *)srvid,
cbtv);
if (svc_pollset != 0)
return (- NIS_CBERROR);
}
break;
default:
}
}
if (svc_pollset != 0)
} else
}
/*
* When we're doing a nis_dump() in the MT rpc.nisd, '__nis_run_dump_callback'
* isn't involved in the actual work. It just signals that the configuration
* is done, waits for completion, and returns the number of times that the
* dispatch function was invoked.
*/
int
{
int count;
(void) mutex_lock(&__nis_dump_mutex);
if (__cbdata_dump == NULL) {
"__nis_run_dump_callback: No dump callback structure");
(void) mutex_unlock(&__nis_dump_mutex);
return (-1);
}
/* Set '__nis_dump_cb_count' to zero so that the dispatch can start */
if (__nis_dump_cb_count < 0) {
__nis_dump_cb_count = 0;
(void) cond_broadcast(&__nis_dump_cv);
}
/* Now it's our turn to wait (for completion) */
while (1) {
int ret;
&to);
/*
* If we've completed, we don't care what cond_wait()
* returned,
*/
if (__cbdata_dump->complete)
break;
/*
* Check when the most recent callback thread exited.
* We know there's none active now, because we're
* holding the lock. If the time stamp still is
* zero, no callback has arrived.
*/
if (__nis_dump_lastcb.tv_usec == 0 &&
__nis_dump_lastcb.tv_sec == 0) {
"__nis_run_dump_callback: Timeout waiting for first callback");
(void) mutex_unlock(&__nis_dump_mutex);
return (-1);
} else {
(void) gettimeofday(&now, 0);
}
"__nis_run_dump_callback: Timeout waiting for callback");
(void) mutex_unlock(&__nis_dump_mutex);
return (-1);
}
}
} else if (ret != 0) {
"__nis_run_dump_callback: Error %d from cond_reltimedwait()",
ret);
(void) mutex_unlock(&__nis_dump_mutex);
return (-1);
}
}
if (__cbdata_dump->cberror != 0) {
} else {
}
(void) mutex_unlock(&__nis_dump_mutex);
return (count);
}
/*
* __do_callback()
*
* This is the dispatcher routine for the callback service. It is
* very simple as you can see.
*/
static void
struct callback_data *__cbdata)
{
union {
} argument;
int result;
return;
case NULLPROC:
return;
case CBPROC_RECEIVE:
break;
case CBPROC_FINISH:
break;
case CBPROC_ERROR:
break;
default:
return;
}
return;
}
}
}
}
static void
}
static void
int waitstat = 0;
/*
* Wait until initialization done by nis_dump(), which is
* signaled by '__nis_dump_cb_count' having a value >= 0.
*/
(void) mutex_lock(&__nis_dump_mutex);
while (__nis_dump_cb_count < 0 && waitstat == 0) {
&timeout);
}
/* Check if someone else has decided that we're done */
if (__cbdata_dump->complete) {
/*
* That "someone else" should have woken up
* __nis_run_dump_callback(), so no need to do
* anything about __nis_dump_cv.
*/
(void) mutex_unlock(&__nis_dump_mutex);
return;
} else if (waitstat != 0) {
"__do_dump_callback: cond error %d waiting for callback initialization",
waitstat);
#ifdef NIS_MT_DEBUG
abort();
#endif /* NIS_MT_DEBUG */
/*
* Since our wait for initialization failed,
* __nis_run_dump_callback() probably isn't running,
* but in order to maximize the chances of recovery,
* set completion and wake it up anyway. In any case,
* we need to wake up any other instances of this routine
* that might be waiting to handle a request.
*/
(void) mutex_unlock(&__nis_dump_mutex);
(void) cond_broadcast(&__nis_dump_cv);
return;
}
/* Increment the call counter */
/*
* Hold the lock so that we don't have to deal with multiple
* instances of the dispatch function.
*/
/*
* Note time when we ended our activity, so that
* __nis_run_dump_callback() knows when it's waited
* long enough.
*/
(void) gettimeofday(&__nis_dump_lastcb, 0);
/*
* We always unlock the mutex. If the callback is complete, we then
* wake up __nis_run_dump_callback(). Since we need to release the
* mutex before signaling on the cv (otherwise, we open a window
* for dead-lock), we first save the 'complete' status.
*/
(void) mutex_unlock(&__nis_dump_mutex);
if (complete)
(void) cond_broadcast(&__nis_dump_cv);
}