1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 #pragma ident   "%Z%%M% %I%     %E% SMI"
  27 
  28 #include <sys/types.h>
  29 #include <stdlib.h>
  30 #include <string.h>
  31 #include <stropts.h>
  32 #include <synch.h>
  33 #include <fcntl.h>
  34 #include <unistd.h>
  35 #include <stdio.h>
  36 #include <dhcp_svc_private.h>
  37 #include <sys/time.h>
  38 #include <dhcpmsg.h>
  39 
  40 #include "dsvclockd.h"
  41 #include "datastore.h"
  42 
  43 static uint32_t         ds_hash(const char *);
  44 
  45 /*
  46  * Create a datastore named `ds_name' and a door which will service requests
  47  * for this datastore.  When the door is called, callback `ds_callback'.
  48  * Returns the created datastore.
  49  */
  50 dsvcd_datastore_t *
  51 ds_create(const char *ds_name, dsvcd_svc_t *ds_callback)
  52 {
  53         char                    door_path[MAXPATHLEN];
  54         dsvcd_datastore_t       *ds = NULL;
  55         int                     fd;
  56         unsigned int            i;
  57         door_info_t             info;
  58 
  59         dhcpmsg(MSG_VERBOSE, "managing locks for datastore `%s'", ds_name);
  60 
  61         ds = malloc(sizeof (dsvcd_datastore_t));
  62         if (ds == NULL) {
  63                 dhcpmsg(MSG_ERR, "cannot manage locks for datastore `%s'",
  64                     ds_name);
  65                 return (NULL);
  66         }
  67 
  68         ds->ds_name = strdup(ds_name);
  69         if (ds->ds_name == NULL) {
  70                 dhcpmsg(MSG_ERR, "cannot manage locks for datastore `%s'",
  71                     ds_name);
  72                 free(ds);
  73                 return (NULL);
  74         }
  75 
  76         ds->ds_doorfd = door_create((void (*)())ds_callback, ds,
  77             DOOR_REFUSE_DESC | DOOR_NO_CANCEL);
  78         if (ds->ds_doorfd == -1) {
  79                 dhcpmsg(MSG_ERR, "cannot create door for datastore `%s'",
  80                     ds_name);
  81                 free(ds->ds_name);
  82                 free(ds);
  83                 return (NULL);
  84         }
  85 
  86         for (i = 0; i < DSVCD_DS_HASH_SIZE; i++) {
  87                 ds->ds_hash[i].cl_head = NULL;
  88                 (void) mutex_init(&ds->ds_hash[i].cl_lock, USYNC_THREAD, 0);
  89         }
  90 
  91         /*
  92          * Create the door name in the filesystem.  First, check to see if
  93          * a door already exists at the specified pathname.  If it does,
  94          * and the server process (no doubt another copy of us) is already
  95          * running, then fail.  Otherwise, unlink the old door and fattach
  96          * a new one.
  97          */
  98         (void) snprintf(door_path, sizeof (door_path), DSVCD_DOOR_FMT, ds_name);
  99 
 100         fd = open(door_path, O_RDWR);
 101         if (fd != -1) {
 102                 if (door_info(fd, &info) == 0 && info.di_target != -1) {
 103                         dhcpmsg(MSG_ERROR, "%s is in use by process %lu",
 104                             door_path, info.di_target);
 105                         (void) close(fd);
 106                         (void) close(ds->ds_doorfd);
 107                         free(ds->ds_name);
 108                         free(ds);
 109                         return (NULL);
 110                 }
 111                 (void) close(fd);
 112                 (void) unlink(door_path);
 113         }
 114 
 115         fd = open(door_path, O_CREAT|O_EXCL|O_RDWR, 0644);
 116         if (fd == -1) {
 117                 dhcpmsg(MSG_ERR, "cannot create door rendezvous for datastore "
 118                     "`%s'", ds_name);
 119                 (void) close(ds->ds_doorfd);
 120                 free(ds->ds_name);
 121                 free(ds);
 122                 return (NULL);
 123         }
 124         (void) close(fd);
 125 
 126         /*
 127          * Attach the door onto the name
 128          */
 129         if (fattach(ds->ds_doorfd, door_path) == -1) {
 130                 dhcpmsg(MSG_ERR, "cannot fattach door rendezvous for datastore "
 131                     "`%s'", ds_name);
 132                 (void) close(ds->ds_doorfd);
 133                 free(ds->ds_name);
 134                 free(ds);
 135                 return (NULL);
 136         }
 137 
 138         return (ds);
 139 }
 140 
 141 /*
 142  * Destroy a datastore `ds' and its associated containers, and remove
 143  * its door from the filesystem.
 144  */
 145 void
 146 ds_destroy(dsvcd_datastore_t *ds)
 147 {
 148         unsigned int            i;
 149         char                    door_path[MAXPATHLEN];
 150         dsvcd_container_t       *cn, *cn_next;
 151 
 152         dhcpmsg(MSG_VERBOSE, "stopping lock management for datastore `%s'",
 153             ds->ds_name);
 154 
 155         /*
 156          * Detach and revoke access to the door.  The detach makes it so
 157          * new callers who open the door will fail; the revoke makes it
 158          * so that callers that already have a door descriptor will fail.
 159          * We do this prior to calling cn_destroy() to make it easier for
 160          * the container lockcount to drain.
 161          */
 162         (void) snprintf(door_path, MAXPATHLEN, DSVCD_DOOR_FMT, ds->ds_name);
 163         (void) fdetach(door_path);
 164         (void) unlink(door_path);
 165         (void) door_revoke(ds->ds_doorfd);
 166         (void) close(ds->ds_doorfd);
 167 
 168         /*
 169          * Destroy all the underlying containers.  We're single-threaded at
 170          * this point, so don't worry about locks.
 171          */
 172         for (i = 0; i < DSVCD_DS_HASH_SIZE; i++) {
 173                 for (cn = ds->ds_hash[i].cl_head; cn != NULL; cn = cn_next) {
 174                         cn_next = cn->cn_next;
 175                         cn_destroy(cn);
 176                 }
 177                 (void) mutex_destroy(&ds->ds_hash[i].cl_lock);
 178         }
 179 
 180         free(ds->ds_name);
 181         free(ds);
 182 }
 183 
 184 /*
 185  * Get a container with id `cn_id' from datastore `ds'; create the
 186  * container if it does not exist.  If `crosshost' is set and the container
 187  * does not yet exist, then the container will synchronize across hosts.  .
 188  * If the container cannot be found or created, NULL is returned.  When the
 189  * calling thread is done with the container, ds_release_container() must
 190  * be called.
 191  */
 192 dsvcd_container_t *
 193 ds_get_container(dsvcd_datastore_t *ds, const char *cn_id, boolean_t crosshost)
 194 {
 195         dsvcd_container_list_t  *cn_list;
 196         dsvcd_container_t       *cn;
 197         uint32_t                idhash = ds_hash(cn_id);
 198 
 199         cn_list = &ds->ds_hash[idhash % DSVCD_DS_HASH_SIZE];
 200         (void) mutex_lock(&cn_list->cl_lock);
 201 
 202         for (cn = cn_list->cl_head; cn != NULL; cn = cn->cn_next) {
 203                 if (idhash == cn->cn_idhash && strcmp(cn_id, cn->cn_id) == 0)
 204                         break;
 205         }
 206 
 207         if (cn == NULL) {
 208                 cn = cn_create(cn_id, crosshost);
 209                 if (cn != NULL) {
 210                         if (cn_list->cl_head != NULL)
 211                                 cn_list->cl_head->cn_prev = cn;
 212 
 213                         cn->cn_next   = cn_list->cl_head;
 214                         cn->cn_prev   = NULL;
 215                         cn_list->cl_head = cn;
 216                         cn->cn_idhash         = idhash;
 217                         cn->cn_nout   = 0;
 218                         cn->cn_lastrel        = 0;
 219                 }
 220         }
 221 
 222         if (cn != NULL)
 223                 cn->cn_nout++;
 224 
 225         (void) mutex_unlock(&cn_list->cl_lock);
 226         return (cn);
 227 }
 228 
 229 /*
 230  * Release a container `cn' belonging to datastore `ds'.  Once a container
 231  * has been released, it can no longer be used by the releasing thread.
 232  * Used to track the number of active instances of a container.
 233  */
 234 void
 235 ds_release_container(dsvcd_datastore_t *ds, dsvcd_container_t *cn)
 236 {
 237         dsvcd_container_list_t  *cn_list;
 238         uint32_t                idhash = ds_hash(cn->cn_id);
 239 
 240         cn_list = &ds->ds_hash[idhash % DSVCD_DS_HASH_SIZE];
 241 
 242         (void) mutex_lock(&cn_list->cl_lock);
 243 
 244         cn->cn_nout--;
 245         cn->cn_lastrel = time(NULL);
 246 
 247         (void) mutex_unlock(&cn_list->cl_lock);
 248 }
 249 
 250 /*
 251  * Destroy any containers in datastore `ds' that have not been accessed in
 252  * the last `idle' seconds.  Return the number of destroyed (reaped)
 253  * containers.
 254  */
 255 unsigned int
 256 ds_reap_containers(dsvcd_datastore_t *ds, unsigned int idle)
 257 {
 258         dsvcd_container_list_t  *cn_list;
 259         dsvcd_container_t       *cn, *cn_next;
 260         unsigned int            i, nreaped = 0;
 261 
 262         for (i = 0; i < DSVCD_DS_HASH_SIZE; i++) {
 263                 cn_list = &ds->ds_hash[i];
 264 
 265                 (void) mutex_lock(&cn_list->cl_lock);
 266                 for (cn = cn_list->cl_head; cn != NULL; cn = cn_next) {
 267                         cn_next = cn->cn_next;
 268 
 269                         /*
 270                          * Since a container is not checked out across a
 271                          * lock operation, we must check if the lock is
 272                          * held as well as the number of instances checked
 273                          * out.
 274                          */
 275                         if (cn->cn_nout != 0 ||
 276                             cn_locktype(cn) != DSVCD_NOLOCK ||
 277                             cn->cn_lastrel + idle >= time(NULL))
 278                                 continue;
 279 
 280                         if (cn == cn_list->cl_head)
 281                                 cn_list->cl_head = cn->cn_next;
 282                         else
 283                                 cn->cn_prev->cn_next = cn->cn_next;
 284 
 285                         if (cn->cn_next != NULL)
 286                                 cn->cn_next->cn_prev = cn->cn_prev;
 287 
 288                         cn_destroy(cn);
 289                         nreaped++;
 290                 }
 291                 (void) mutex_unlock(&cn_list->cl_lock);
 292         }
 293 
 294         return (nreaped);
 295 }
 296 
 297 /*
 298  * Hash a container identified by `cn_id' into a 32-bit unsigned integer
 299  * suitable for use as a key in a hash table.
 300  */
 301 static uint32_t
 302 ds_hash(const char *cn_id)
 303 {
 304         uint32_t        result = 0;
 305         unsigned int    i;
 306 
 307         for (i = 0; cn_id[i] != '\0'; i++)
 308                 result += cn_id[i] << i;
 309 
 310         return (result);
 311 }