dsvclockd.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"
/*
* #define _POSIX_PTHREAD_SEMANTICS before #including <signal.h> so that we
* get the right (POSIX) version of sigwait(2).
*/
#define _POSIX_PTHREAD_SEMANTICS
#include <sys/sysmacros.h>
#include <dhcp_svc_private.h>
#include <pthread.h>
#include <stdlib.h>
#include <dhcpmsg.h>
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <locale.h>
#include <synch.h>
#include "datastore.h"
#include "dsvclockd.h"
/*
* The DHCP service daemon synchronizes access to containers within a given
* datastore. Any datastore which is willing to accept the synchronization
* constraints imposed by the DHCP service daemon can use this daemon in
* lieu of rolling their own synchronization code.
*
* See $SRC/lib/libdhcpsvc/private/README.synch for more information.
*/
#ifndef TEXT_DOMAIN
#define TEXT_DOMAIN "SYS_TEST"
#endif
/*
* Unlock descriptor -- one for each lock granted. This descriptor is used
* to subsequently unlock the granted lock (and to synchronize unlocking of
* the lock; see svc_unlock() below for details).
*/
typedef struct dsvcd_unlock_desc {
int ud_fd;
struct dsvcd_unlock_desc *ud_next;
static unsigned int ud_reclaim_count = 0;
static void *reaper(void *);
static int daemonize(void);
static void *stack_create(unsigned int *);
static void stack_destroy(void *, unsigned int);
static void doorserv_create(door_info_t *);
int
{
char **modules;
unsigned int i, j;
int debug_level = 0;
char signame[SIG2STR_MAX];
char *progname;
void *stackbase;
(void) textdomain(TEXT_DOMAIN);
/*
* Mask all signals except SIGABRT; doing this here ensures that
* all threads created through door_create() have them masked too.
*/
(void) sigfillset(&sigset);
/*
* Figure out our program name; just keep the final piece so that
* our dhcpmsg() messages don't get too long.
*/
progname++;
else
/*
* Set the door thread creation procedure so that all of our
* threads are created with thread stacks with backing store.
*/
(void) door_server_create(doorserv_create);
switch (c) {
case 'd':
break;
case 'f':
break;
case 'v':
is_verbose = B_TRUE;
break;
case '?':
return (EXIT_FAILURE);
default:
break;
}
}
if (geteuid() != 0) {
dhcpmsg_fini();
return (EXIT_FAILURE);
}
dhcpmsg_fini();
return (EXIT_FAILURE);
}
(void) atexit(dhcpmsg_fini);
/*
* Max out the number available descriptors since we need to
* allocate two per held lock.
*/
return (EXIT_FAILURE);
}
/*
* NOTE: this code assumes that a module that needs dsvclockd will
* always need it (even as the container version is ramped). If
* this becomes bogus in a future release, we'll have to make this
* logic more sophisticated.
*/
for (i = 0; i < nmodules; i++) {
"type for `%s', skipping", modules[i]);
nsynchmods--;
continue;
}
nsynchmods--;
}
}
if (nsynchmods == 0) {
return (EXIT_SUCCESS);
}
/*
* Allocate the datastore table; include one extra entry so that
* the table is NULL-terminated.
*/
return (EXIT_FAILURE);
}
/*
* Create the datastores (which implicitly creates the doors).
* then sit around and wait for requests to come in on the doors.
*/
for (i = 0, j = 0; i < nmodules; i++) {
while (j-- > 0)
ds_destroy(ds_table[j]);
return (EXIT_FAILURE);
}
j++;
}
}
"will not be reaped");
else {
THR_DAEMON, NULL);
if (errno != 0) {
"containers will not be reaped");
}
}
/*
* Synchronously wait for a QUIT, TERM, or INT, then shutdown.
*/
(void) sigemptyset(&sigset);
for (i = 0; i < nsynchmods; i++)
ds_destroy(ds_table[i]);
return (EXIT_SUCCESS);
}
/*
* Sanity check that dsvcd_request_t `req' (which is `reqsize' bytes long)
* is a correctly formed request; if not, return an error which will be
* returned to the door caller.
*/
static int
{
return (DSVC_SYNCH_ERR);
}
/*
* Check credentials; we don't allow any non-super-user requests
* since this would open a denial-of-service hole (since a lock
* could be checked out indefinitely).
*/
return (DSVC_ACCESS);
}
return (DSVC_ACCESS);
}
/*
* Check the version and size; we check this before checking the
* size of the request structure since an "incompatible version"
* message is more helpful than a "short request" message.
*/
req->rq_version);
return (DSVC_SYNCH_ERR);
}
return (DSVC_SYNCH_ERR);
}
return (DSVC_SUCCESS);
}
/*
* Service a lock request `req' passed across the door for datastore `ds'.
* After verifying that the request is well-formed, locks the container and
* creates an "unlock" door descriptor that the client uses to unlock the
* door (either explicitly through door_call()) or implicitly through
* terminating abnormally).
*/
/* ARGSUSED */
static void
{
char conid[MAXPATHLEN];
unsigned int attempts = 0;
sizeof (dsvcd_lock_request_t));
return;
}
/*
* Verify that this is a lock request; if in the future we support
* other requests, we'll have to abstract this a bit.
*/
return;
}
lreq->lrq_locktype);
return;
}
/*
* Find the container; create if it doesn't already exist. We do
* this as a single operation to avoid race conditions.
*/
return;
}
/*
* We need another door descriptor which is passed back with the
* request. This descriptor is used when the caller wants to
* gracefully unlock or when the caller terminates abnormally.
*/
return;
}
/*
* We pass a duped door descriptor with the DOOR_RELEASE flag set
* instead of just passing the descriptor itself to handle the case
* where the client has gone away before we door_return(). Since
* we duped, the door descriptor itself will have a refcount of 2
* when we go to pass it to the client; if the client does not
* exist, the DOOR_RELEASE will drop the count from 2 to 1 which
* will cause a DOOR_UNREF_DATA call.
*
* In the regular (non-error) case, the door_return() will handoff
* the descriptor to the client, bumping the refcount to 3, and
* then the DOOR_RELEASE will drop the count to 2. If the client
* terminates abnormally after this point, the count will drop from
* 2 to 1 which will cause a DOOR_UNREF_DATA call. If the client
* unlocks gracefully, the refcount will still be 2 when the unlock
* door server procedure is called, and the unlock procedure will
* unlock the lock and note that the lock has been unlocked (so
* that we know the DOOR_UNREF_DATA call generated from the client
* subsequently closing the unlock descriptor is benign).
*
* Note that a DOOR_UNREF_DATA call will be generated *any time*
* the refcount goes from 2 to 1 -- even if *we* cause it to
* happen, which by default will happen in some of the error logic
* below (when we close the duped descriptor). To prevent this
* scenario, we tell ud_destroy() *not* to cache the unlock
* descriptor, which forces it to blow away the descriptor using
* door_revoke(), making the close() that follows benign.
*/
return;
}
/*
* Acquire the actual read or write lock on the container.
*/
return;
}
if (lreq->lrq_nonblock) {
" is out of file descriptors");
NULL, 0);
return;
}
if (attempts++ == 0) {
" is out of file descriptors (retrying)");
}
}
}
/*
* Service an unlock request `req' passed across the door associated with
* the unlock token `cookie'. We may be called explicitly (in which case
* the request is a well-formed dsvcd_request_t) or implicitly (in which
* case our request is set to the value DOOR_UNREF_DATA); this latter case
* occurs when a process holding a lock terminates. In either case, unlock
* the lock; in the implicit case, log a message as well.
*/
/* ARGSUSED */
static void
{
/*
* Although unlock descriptors are handed out to only a single
* thread who has been granted a lock (ergo it seems that only one
* thread should be able to call us back), there's a potential race
* here if the process crashes while in this door_call(), since
* both this thread and the unref kernel upcall thread may run at
* the same time. Protect against this case with a mutex.
*/
/*
* First handle the case where the lock owner has closed the unlock
* descriptor, either because they have unlocked the lock and are
* thus done using the descriptor, or because they crashed. In the
* second case, print a message.
*/
if (req == DOOR_UNREF_DATA) {
/*
* The last reference is ours; we can free the descriptor.
*/
/*
* Normal case: the caller is closing the unlock descriptor
* on a lock they've already unlocked -- just return.
*/
return;
}
/*
* Error case: the caller has crashed while holding the
* unlock descriptor (or is otherwise in violation of
* protocol). Since all datastores are required to be
* robust even if unexpected termination occurs, we assume
* the container is not corrupt, even if the process
* crashed with the write lock held.
*/
switch (cn_locktype(cn)) {
case DSVCD_RDLOCK:
break;
case DSVCD_WRLOCK:
"may or may not have succeeded");
break;
case DSVCD_NOLOCK:
break;
}
return;
}
/*
* Verify that this is a unlock request; if in the future we support
* other requests, we'll have to abstract this a bit.
*/
sizeof (dsvcd_unlock_request_t));
return;
}
return;
}
/*
* Attempt to unlock an already-unlocked container; log and return.
*/
return;
}
/*
* Unlock the container; note that after cn_unlock() has been done
* cn->cn_id is no longer accessible.
*/
/*
* Even though we've unlocked the lock, we cannot yet destroy the
* unlock descriptor (even if we revoke the door) because it's
* possible the unref thread is already waiting on ud_lock.
*/
}
/*
* Reap containers that have not been recently used.
*/
static void *
reaper(void *ds_table_raw)
{
unsigned int i, nreaped;
for (;;) {
(void) sleep(DSVCD_REAP_INTERVAL);
if (nreaped > 0) {
"synchpoints from %s", nreaped,
}
}
}
/* NOTREACHED */
return (NULL);
}
/*
* Daemonize the process.
*/
static int
daemonize(void)
{
switch (fork()) {
case -1:
return (0);
case 0:
/*
* Lose our controlling terminal, and become both a session
* leader and a process group leader.
*/
if (setsid() == -1)
return (0);
/*
* Under POSIX, a session leader can accidentally (through
* open(2)) acquire a controlling terminal if it does not
* have one. Just to be safe, fork() again so we are not a
* session leader.
*/
switch (fork()) {
case -1:
return (0);
case 0:
(void) chdir("/");
(void) umask(022);
closefrom(0);
break;
default:
}
break;
default:
}
return (1);
}
/*
* Create an unlock descriptor for container `cn' -- returns an unlock
* descriptor on success, or NULL on failure; the reason for failure is in
* `retvalp'. Since creating door descriptors is expensive, we keep a few
* cache a small list of old descriptors around on a reclaim list and only
* allocate a new one if the list is empty.
*/
static dsvcd_unlock_desc_t *
{
*retvalp = DSVC_SUCCESS;
(void) mutex_lock(&ud_reclaim_lock);
if (ud_reclaim_list != NULL) {
(void) mutex_unlock(&ud_reclaim_lock);
} else {
(void) mutex_unlock(&ud_reclaim_lock);
return (NULL);
}
return (NULL);
}
}
return (ud);
}
/*
* Destroy the unlock descriptor `ud' -- `ud' must be unlocked on entry.
* If there's room and `cacheable' is set, then, keep the unlock descriptor
* on the reclaim list to lower future creation cost.
*/
static void
{
(void) mutex_lock(&ud_reclaim_lock);
(void) mutex_unlock(&ud_reclaim_lock);
} else {
(void) mutex_unlock(&ud_reclaim_lock);
}
}
/*
* Create a stack of `*stacksizep' bytes (rounded up to the nearest page)
* including a redzone for catching stack overflow. Set `stacksizep' to
* point to the actual usable size of the stack (i.e., everything but the
* redzone). Returns a pointer to the base of the stack (not including the
* redzone).
*/
static void *
stack_create(unsigned int *stacksizep)
{
unsigned int stacksize = *stacksizep;
if (stackbase == MAP_FAILED)
return (NULL);
*stacksizep = stacksize;
}
/*
* Destroy the stack of `stacksize' bytes pointed to by `stackbase'.
*/
static void
{
}
/*
* Start function for door server threads; turns off thread cancellation
* and then parks in the kernel via door_return().
*/
/* ARGSUSED */
static void *
doorserv_thread(void *arg)
{
return (NULL);
}
/*
* Creation function for door server threads. We require door threads to
* have 32K of backed stack. This is a guess but will be more than
* sufficient for our uses, since door threads have a shallow call depth
* and the functions use little automatic storage.
*/
/* ARGSUSED */
static void
{
void *stackbase;
if (errno != 0) {
"server thread pool will not be grown");
}
}
}